chore: Update radicle to 0.24.0

Update to `radicle-0.24.0`. This update introduces the change of the
`radicle::cob::store` API changes. The `Store` structure now requires
an `Access` parameter. This is fulfilled by `access::ReadOnly` or
`access::WriteAs`. `ReadOnly` is trivially constructed, and provides
read access to `Jobs`. `WriteAs` is constructed with the signer of the
node, and provides read and write access to `Jobs`. Since the signer
is provided at construction, it is no longer needed to be passed into
`Jobs` operations.

This necessarily updates the MSRV to 1.86.0 since transitive crates
required a later version.
This commit is contained in:
Fintan Halpenny
2026-05-07 08:41:46 +01:00
parent 4d37a30cb3
commit 536de30896
5 changed files with 791 additions and 473 deletions
+12
View File
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Breaking Changes
- Update to `radicle-0.24.0`. This update introduces the change of the
`radicle::cob::store` API changes. The `Store` structure now requires an
`Access` parameter. This is fulfilled by `access::ReadOnly` or
`access::WriteAs`. `ReadOnly` is trivially constructed, and provides read
access to `Jobs`. `WriteAs` is constructed with the signer of the node, and
provides read and write access to `Jobs`. Since the signer is provided at
construction, it is no longer needed to be passed into `Jobs` operations.
- The MSRV is updated to `1.86.0` due to transitive dependencies of
`radicle-0.24.0`.
## 0.5.2
### Changes
Generated
+642 -343
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -9,7 +9,7 @@ authors = ["Fintan Halpenny <fintan.halpenny@gmail.com>"]
edition = "2021"
# N.B. always use the full semver so that rust-overlay in `flake.nix` can select
# the correct version
rust-version = "1.85.1"
rust-version = "1.86.0"
[dependencies]
chrono = { version = "0.4.42", default-features = false }
@@ -18,13 +18,12 @@ indexmap = { version = "2.7.1", features = ["serde"] }
nonempty = "0.11.0"
once_cell = "1.20.3"
qcheck = "1.0.0"
radicle = { version = "0.22.0" }
radicle = { version = "0.24" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.140"
thiserror = "2.0.11"
url = { version = "2.5.4", features = ["serde"] }
uuid = { version = "1.13.1", features = ["serde", "v4"] }
[dev-dependencies.radicle]
version = "0.22.0"
features = ["test"]
[dev-dependencies]
radicle = { version = "0.24", features = ["test"] }
+62 -43
View File
@@ -8,9 +8,12 @@ use std::{collections::BTreeSet, error::Error as _, time::Duration};
use clap::Parser;
use uuid::Uuid;
use radicle::cob;
use radicle::cob::store::access;
use radicle::crypto;
use radicle::{
cob, crypto,
crypto::signature::Signer,
cob::store::access::Access,
crypto::signature,
git::Oid,
node::{
device::Device,
@@ -89,12 +92,16 @@ fn profile() -> Result<Profile, error::Profile> {
Profile::load().map_err(error::Profile)
}
fn jobs(repo: &Repository) -> Result<Jobs<'_, Repository>, error::Jobs> {
Jobs::open(repo).map_err(|err| error::Jobs { rid: repo.id, err })
fn jobs<'a, S>(
repo: &'a Repository,
signer: &'a Device<S>,
) -> Result<Jobs<'a, Repository, access::WriteAs<'a, Device<S>>>, error::Jobs> {
let write_as = access::WriteAs::new(signer);
Jobs::open(repo, write_as).map_err(|err| error::Jobs { rid: repo.id, err })
}
fn announce(profile: &Profile, repo_id: RepoId) -> Result<(), error::Announce> {
let mut node = Node::new(profile.home.socket());
let mut node = Node::new(profile.socket_from_env());
let (synced, unsynced) = node
.seeds_for(repo_id, [*profile.id()])
@@ -132,34 +139,31 @@ fn run(args: Args) -> Result<(), RadJobError> {
let profile = profile()?;
let repo = args.repository(&profile)?;
let mut jobs = jobs(&repo)?;
let signer = profile.signer().map_err(error::Signer)?;
let mut jobs = jobs(&repo, &signer)?;
match args.command {
Command::New(command) => {
let signer = profile.signer().map_err(error::Signer)?;
new_job(command, &mut jobs, &signer)?;
new_job(command, &mut jobs)?;
if !args.no_sync {
announce(&profile, repo.id)?
}
}
Command::List(command) => list_jobs(command, &jobs)?,
Command::Run(command) => {
let signer = profile.signer().map_err(error::Signer)?;
run_job(command, &mut jobs, &signer)?;
run_job(command, &mut jobs)?;
if !args.no_sync {
announce(&profile, repo.id)?
}
}
Command::Show(command) => show_job(command, &jobs)?,
Command::Failed(command) => {
let signer = profile.signer().map_err(error::Signer)?;
failed_job(command, &mut jobs, &signer)?;
failed_job(command, &mut jobs)?;
if !args.no_sync {
announce(&profile, repo.id)?
}
}
Command::Succeeded(command) => {
let signer = profile.signer().map_err(error::Signer)?;
succeeded_job(command, &mut jobs, &signer)?;
succeeded_job(command, &mut jobs)?;
if !args.no_sync {
announce(&profile, repo.id)?
}
@@ -169,57 +173,66 @@ fn run(args: Args) -> Result<(), RadJobError> {
Ok(())
}
fn run_job<G>(
fn run_job<'a, S>(
command::Run { oid, url }: command::Run,
jobs: &mut Jobs<'_, Repository>,
signer: &Device<G>,
jobs: &mut Jobs<'a, Repository, access::WriteAs<'a, S>>,
) -> Result<(), error::Run>
where
G: Signer<crypto::Signature>,
S: signature::Signer<crypto::ssh::ExtendedSignature>
+ signature::Signer<crypto::Signature>
+ signature::Verifier<crypto::Signature>
+ crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
{
let uuid = Uuid::new_v4();
let (id, job) = find_by_commit(oid, jobs)?;
let mut job = JobMut::new(id, job, jobs);
job.run(uuid, url, signer)
job.run(uuid, url)
.map_err(|err| error::Run::Store { id, err })?;
println!("{uuid}");
Ok(())
}
fn succeeded_job<G>(
fn succeeded_job<'a, S>(
command::Succeeded { oid, run }: command::Succeeded,
jobs: &mut Jobs<'_, Repository>,
signer: &Device<G>,
jobs: &mut Jobs<'a, Repository, access::WriteAs<'a, S>>,
) -> Result<(), error::Succeeded>
where
G: Signer<crypto::Signature>,
S: signature::Signer<crypto::ssh::ExtendedSignature>
+ signature::Signer<crypto::Signature>
+ signature::Verifier<crypto::Signature>
+ crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
{
let (id, job) = find_by_commit(oid, jobs)?;
let mut job = JobMut::new(id, job, jobs);
job.finish(run, Reason::Succeeded, signer)
job.finish(run, Reason::Succeeded)
.map_err(|err| error::Succeeded::Store { id, err })?;
Ok(())
}
fn failed_job<G>(
fn failed_job<'a, S>(
command::Failed { oid, run }: command::Failed,
jobs: &mut Jobs<'_, Repository>,
signer: &Device<G>,
jobs: &mut Jobs<'a, Repository, access::WriteAs<'a, S>>,
) -> Result<(), error::Failed>
where
G: Signer<crypto::Signature>,
S: signature::Signer<crypto::ssh::ExtendedSignature>
+ signature::Signer<crypto::Signature>
+ signature::Verifier<crypto::Signature>
+ crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
{
let (id, job) = find_by_commit(oid, jobs)?;
let mut job = JobMut::new(id, job, jobs);
job.finish(run, Reason::Failed, signer)
job.finish(run, Reason::Failed)
.map_err(|err| error::Failed::Store { id, err })?;
Ok(())
}
fn list_jobs(
fn list_jobs<A>(
command::List { pretty, verbose }: command::List,
jobs: &Jobs<'_, Repository>,
) -> Result<(), error::List> {
jobs: &Jobs<'_, Repository, A>,
) -> Result<(), error::List>
where
A: Access,
{
let iter = jobs
.all()
.map_err(error::List::All)?
@@ -244,25 +257,28 @@ fn list_jobs(
Ok(())
}
fn new_job<G>(
fn new_job<'a, S>(
command::New { oid }: command::New,
jobs: &mut Jobs<Repository>,
signer: &Device<G>,
jobs: &mut Jobs<'a, Repository, access::WriteAs<'a, S>>,
) -> Result<(), error::New>
where
G: Signer<crypto::Signature>,
S: signature::Signer<crypto::ssh::ExtendedSignature>
+ signature::Signer<crypto::Signature>
+ signature::Verifier<crypto::Signature>
+ crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
{
let job = jobs
.create(oid, signer)
.map_err(|err| error::New { oid, err })?;
let job = jobs.create(oid).map_err(|err| error::New { oid, err })?;
println!("{}", job.id());
Ok(())
}
fn show_job(
fn show_job<A>(
command::Show { pretty, oid }: command::Show,
jobs: &Jobs<Repository>,
) -> Result<(), error::Show> {
jobs: &Jobs<Repository, A>,
) -> Result<(), error::Show>
where
A: Access,
{
let (id, job) = find_by_commit(oid, jobs)?;
let show = radicle_job::display::Job::new(id, &job);
if pretty {
@@ -276,7 +292,10 @@ fn show_job(
Ok(())
}
fn find_by_commit(oid: Oid, jobs: &Jobs<Repository>) -> Result<(JobId, Job), error::Find> {
fn find_by_commit<A>(oid: Oid, jobs: &Jobs<Repository, A>) -> Result<(JobId, Job), error::Find>
where
A: Access,
{
jobs.find_by_commit(oid)
.map_err(|err| error::Find::FindCommit { oid, err })?
.next()
+71 -82
View File
@@ -12,6 +12,7 @@
//!
//! ```
//! # use radicle::crypto::Signer;
//! # use radicle::cob::store::access;
//! # use radicle::git::{raw::Repository, Oid};
//! # use radicle::test;
//! # use url::Url;
@@ -37,14 +38,14 @@
//! # } = test::setup::NodeWithRepo::default();
//! # let oid = commit(&repo.backend);
//! # let repo = (&*repo).clone();
//! let mut jobs = Jobs::open(repo).unwrap();
//! #
//! # let test::setup::NodeWithRepo { node: bob, .. } = test::setup::NodeWithRepo::default();
//! let mut job = jobs.create(oid, &alice.signer).unwrap();
//! let mut jobs = Jobs::open(repo, access::WriteAs::new(&bob.signer)).unwrap();
//! #
//! let mut job = jobs.create(oid).unwrap();
//! #
//! let uuid = Uuid::new_v4();
//! let log = Url::parse(&format!("https://example.com/ci/logs?run={}", uuid)).unwrap();
//! job.run(uuid, log, &alice.signer).unwrap();
//! job.run(uuid, log).unwrap();
//! ```
#![deny(missing_docs)]
@@ -56,11 +57,11 @@ use std::str::FromStr;
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use radicle::cob::store::Cob;
use radicle::cob::{self, store, EntryId, Evaluate, ObjectId, Op, TypeName};
use radicle::cob;
use radicle::cob::store::{access, Cob};
use radicle::cob::{store, EntryId, Evaluate, ObjectId, Op, TypeName};
use radicle::crypto;
use radicle::crypto::signature::Signer;
use radicle::node::device::Device;
use radicle::crypto::signature;
use radicle::node::NodeId;
use radicle::prelude::ReadRepository;
use radicle::storage::{RepositoryError, SignRepository, WriteRepository};
@@ -590,26 +591,27 @@ pub enum Reason {
///
/// - [`Jobs::create`]
/// - [`Jobs::get_mut`]
pub struct Jobs<'a, R> {
raw: store::Store<'a, Job, R>,
pub struct Jobs<'a, R, Access> {
raw: store::Store<'a, Job, R, Access>,
}
impl<'a, R> Deref for Jobs<'a, R> {
type Target = store::Store<'a, Job, R>;
impl<'a, R, A> Deref for Jobs<'a, R, A> {
type Target = store::Store<'a, Job, R, A>;
fn deref(&self) -> &Self::Target {
&self.raw
}
}
impl<'a, R> Jobs<'a, R>
impl<'a, R, A> Jobs<'a, R, A>
where
R: ReadRepository + cob::Store<Namespace = NodeId>,
A: access::Access + 'a,
{
/// Open a jobs store.
pub fn open(repository: &'a R) -> Result<Self, RepositoryError> {
pub fn open(repository: &'a R, access: A) -> Result<Self, RepositoryError> {
let identity = repository.identity_head()?;
let raw = store::Store::open(repository)?.identity(identity);
let raw = store::Store::open_for(&TYPENAME, repository, access)?.identity(identity);
Ok(Self { raw })
}
@@ -638,9 +640,10 @@ pub struct FindByCommit<'a> {
}
impl<'a> FindByCommit<'a> {
fn new<R>(jobs: &Jobs<'a, R>, needle: Oid) -> Result<Self, cob::store::Error>
fn new<R, A>(jobs: &Jobs<'a, R, A>, needle: Oid) -> Result<Self, cob::store::Error>
where
R: ReadRepository + cob::Store<Namespace = NodeId>,
A: access::Access + 'a,
{
Ok(Self {
jobs: Box::new(jobs.all()?),
@@ -668,12 +671,16 @@ impl Iterator for FindByCommit<'_> {
}
}
impl<'a, R> Jobs<'a, R>
impl<'a, R, S> Jobs<'a, R, access::WriteAs<'a, S>>
where
R: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
S: signature::Signer<crypto::ssh::ExtendedSignature>
+ signature::Signer<crypto::Signature>
+ signature::Verifier<crypto::Signature>
+ crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
{
/// Get a [`JobMut`], given its [`JobId`] identifier.
pub fn get_mut<'g>(&'g mut self, id: &JobId) -> Result<JobMut<'a, 'g, R>, store::Error> {
pub fn get_mut<'g>(&'g mut self, id: &JobId) -> Result<JobMut<'a, 'g, R, S>, store::Error> {
let job = self
.raw
.get(id.as_object_id())?
@@ -687,19 +694,11 @@ where
}
/// Create a new [`Job`] in the repository.
pub fn create<'g, G>(
&'g mut self,
oid: Oid,
signer: &Device<G>,
) -> Result<JobMut<'a, 'g, R>, store::Error>
where
G: Signer<crypto::Signature>,
{
let (id, job) = store::Transaction::initial::<_, _, Transaction<R>>(
pub fn create<'g>(&'g mut self, oid: Oid) -> Result<JobMut<'a, 'g, R, S>, store::Error> {
let (id, job) = store::Transaction::initial(
"Request job",
&mut self.raw,
signer,
|tx, _| {
|tx: &mut Transaction<R>, _| {
tx.request(oid)?;
Ok(())
},
@@ -715,15 +714,15 @@ where
/// A `JobMut` is a [`Job`] where the underlying `Job` can be mutated by
/// applying actions to it.
pub struct JobMut<'a, 'g, R> {
pub struct JobMut<'a, 'g, R, Signer> {
/// Git object that the [`Job`] applies to.
pub id: JobId,
job: Job,
store: &'g mut Jobs<'a, R>,
store: &'g mut Jobs<'a, R, access::WriteAs<'a, Signer>>,
}
impl<R> Deref for JobMut<'_, '_, R> {
impl<R, Signer> Deref for JobMut<'_, '_, R, Signer> {
type Target = Job;
fn deref(&self) -> &Self::Target {
@@ -731,12 +730,16 @@ impl<R> Deref for JobMut<'_, '_, R> {
}
}
impl<'a, 'g, R> JobMut<'a, 'g, R>
impl<'a, 'g, R, S> JobMut<'a, 'g, R, S>
where
R: WriteRepository + cob::Store<Namespace = NodeId>,
S: signature::Signer<crypto::ssh::ExtendedSignature>
+ signature::Signer<crypto::Signature>
+ signature::Verifier<crypto::Signature>
+ crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
{
/// Create a new `JobMut`.
pub fn new(id: JobId, job: Job, store: &'g mut Jobs<'a, R>) -> Self {
pub fn new(id: JobId, job: Job, store: &'g mut Jobs<'a, R, access::WriteAs<'a, S>>) -> Self {
Self { id, job, store }
}
@@ -757,48 +760,25 @@ where
/// Start a new [`Run`] for the node, where the run is identified by the
/// given [`Uuid`].
pub fn run<G>(
&mut self,
uuid: Uuid,
log: Url,
signer: &Device<G>,
) -> Result<EntryId, store::Error>
where
G: Signer<crypto::Signature>,
{
self.transaction("Run node job", signer, |tx| tx.run(uuid, log))
pub fn run(&mut self, uuid: Uuid, log: Url) -> Result<EntryId, store::Error> {
self.transaction("Run node job", |tx| tx.run(uuid, log))
}
/// Finish a [`Run`], identified by the given [`Uuid`], for the node, with
/// the provided [`Reason`].
pub fn finish<G>(
&mut self,
uuid: Uuid,
reason: Reason,
signer: &Device<G>,
) -> Result<EntryId, store::Error>
where
G: Signer<crypto::Signature>,
{
self.transaction("Finished node job", signer, |tx| tx.finish(uuid, reason))
pub fn finish(&mut self, uuid: Uuid, reason: Reason) -> Result<EntryId, store::Error> {
self.transaction("Finished node job", |tx| tx.finish(uuid, reason))
}
/// Apply COB operations to a `JobMut`.
fn transaction<G, F>(
&mut self,
message: &str,
signer: &Device<G>,
operations: F,
) -> Result<EntryId, store::Error>
fn transaction<F>(&mut self, message: &str, operations: F) -> Result<EntryId, store::Error>
where
G: Signer<crypto::Signature>,
F: FnOnce(&mut Transaction<R>) -> Result<(), store::Error>,
{
let mut tx = Transaction::default();
operations(&mut tx)?;
let (job, commit) =
tx.0.commit(message, self.id.into(), &mut self.store.raw, signer)?;
let (job, commit) = tx.0.commit(message, self.id.into(), &mut self.store.raw)?;
self.job = job;
Ok(commit)
@@ -879,6 +859,7 @@ where
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod test {
use radicle::cob::store::access;
use radicle::git::{raw::Repository, Oid};
use radicle::test;
use url::Url;
@@ -911,47 +892,53 @@ mod test {
node: alice, repo, ..
} = test::setup::NodeWithRepo::default();
let oid = commit(&repo.backend);
let mut jobs = Jobs::open(&*repo).unwrap();
let mut alice_jobs = Jobs::open(&*repo, access::WriteAs::new(&alice.signer)).unwrap();
let test::setup::NodeWithRepo { node: bob, .. } = test::setup::NodeWithRepo::default();
let mut job = jobs.create(oid, &alice.signer).unwrap();
let mut bob_jobs = Jobs::open(&*repo, access::WriteAs::new(&bob.signer)).unwrap();
let mut alice_job = alice_jobs.create(oid).unwrap();
let (alice_uuid, alice_log) = node_run();
job.run(alice_uuid, alice_log.clone(), &alice.signer)
.unwrap();
alice_job.run(alice_uuid, alice_log.clone()).unwrap();
let (bob_uuid, bob_log) = node_run();
job.run(bob_uuid, bob_log.clone(), &bob.signer).unwrap();
let mut bob_job = bob_jobs.get_mut(&alice_job.id).unwrap();
bob_job.run(bob_uuid, bob_log.clone()).unwrap();
let alice_runs = job.runs_of(alice.signer.public_key()).unwrap();
let alice_runs = bob_job.runs_of(alice.signer.public_key()).unwrap();
assert!(alice_runs.contains_key(&alice_uuid));
let run = alice_runs.get(&alice_uuid).unwrap();
assert_eq!(run.status, Status::Started);
assert_eq!(run.log, alice_log);
let bob_runs = job.runs_of(bob.signer.public_key()).unwrap();
let bob_runs = bob_job.runs_of(bob.signer.public_key()).unwrap();
assert!(bob_runs.contains_key(&bob_uuid));
let run = bob_runs.get(&bob_uuid).unwrap();
assert_eq!(run.status, Status::Started);
assert_eq!(run.log, bob_log);
job.finish(alice_uuid, Reason::Succeeded, &alice.signer)
.unwrap();
alice_job.finish(alice_uuid, Reason::Succeeded).unwrap();
let finished = job.finished();
let finished = alice_job.finished();
assert!(finished.contains_key(alice.signer.public_key()));
assert!(!finished.contains_key(bob.signer.public_key()));
job.finish(bob_uuid, Reason::Failed, &bob.signer).unwrap();
bob_job.finish(bob_uuid, Reason::Failed).unwrap();
let succeeded = job.succeeded();
let succeeded = bob_job.succeeded();
assert!(succeeded.contains_key(alice.signer.public_key()));
assert!(!succeeded.contains_key(bob.signer.public_key()));
let failed = job.failed();
let failed = bob_job.failed();
assert!(!failed.contains_key(alice.signer.public_key()));
assert!(failed.contains_key(bob.signer.public_key()));
let started = job.started();
let started = bob_job.started();
assert!(started.is_empty());
// Check that we still have alice's initial log
let alice_runs = alice_job.runs_of(alice.signer.public_key()).unwrap();
let run = alice_runs.get(&alice_uuid).unwrap();
assert_eq!(run.log, alice_log);
}
#[test]
@@ -959,9 +946,10 @@ mod test {
let test::setup::NodeWithRepo {
node: alice, repo, ..
} = test::setup::NodeWithRepo::default();
let mut jobs = Jobs::open(&*repo).unwrap();
let access = access::WriteAs::new(&alice.signer);
let mut jobs = Jobs::open(&*repo, access).unwrap();
let oid = test::arbitrary::oid();
let job = jobs.create(oid, &alice.signer);
let job = jobs.create(oid);
assert!(job.is_err())
}
@@ -971,13 +959,14 @@ mod test {
node: alice, repo, ..
} = test::setup::NodeWithRepo::default();
let oid = commit(&repo.backend);
let mut jobs = Jobs::open(&*repo).unwrap();
let access = access::WriteAs::new(&alice.signer);
let mut jobs = Jobs::open(&*repo, access).unwrap();
let job1 = {
let job1 = jobs.create(oid, &alice.signer).unwrap();
let job1 = jobs.create(oid).unwrap();
job1.id
};
let job2 = {
let job2 = jobs.create(oid, &alice.signer).unwrap();
let job2 = jobs.create(oid).unwrap();
job2.id
};