rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwoodfca3c519c64da0c0a79ce3f81bd035ad8b8a3c9a
{
"request": "trigger",
"version": 1,
"event_type": "patch",
"repository": {
"id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"private": false,
"default_branch": "master",
"delegates": [
"did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
"did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"action": "Created",
"patch": {
"id": "0f07db98214142b63b138f790969f44117d4b75d",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"title": "Introduce `git::repository::user`",
"state": {
"status": "open",
"conflicts": []
},
"before": "fb4d84927c228d59d48afb997ff35385a2898f95",
"after": "fca3c519c64da0c0a79ce3f81bd035ad8b8a3c9a",
"commits": [
"fca3c519c64da0c0a79ce3f81bd035ad8b8a3c9a",
"175e3217b49f52d3542b9d9602c65d06a70e700a",
"d990739ade2fde235ba5698b5570bf6be5efa25e",
"d26c62e1fe159b9ea3cd694cef9a50b516196d4b",
"af27f7fbcc9cbb0224f3b25222662fcbc3a84590",
"2f84deba9aa1d1b7aeaf1efba35616592d51df47",
"9a0bf85dfcb17625cfec0b564d5091e26a04982a",
"b3334426dc9ee4d1432dc78fad681b81f5bd87e4"
],
"target": "919d5c780c4da0b5b9f18f1549bc59dffd7125af",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "0f07db98214142b63b138f790969f44117d4b75d",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Introduce a new module, `user`, that encapsulates logic for references\nin user space.\n\nThe current implementation of this is having supplying `NodeId`\nalongside a `Qualified` reference name, to get the form\n`refs/namespaces/<node id>/<qualifed refname>`.\nThe entire code base should not need to know this, yet this detail is\nleaked in an ad-hoc manner.\nOn top of this, using `NodeId` creates a stronger tie to this\nimplementation, where we know we want move towards a more flexible\nform, e.g. other `Did`s, in the future.\n\nThe `user` module introduces two types:\n1. `Namespace`: captures a user's `Did` and a generic repository, so\nthat it can query a user's reference space.\n2. `Namespaces`: captures a generic repository, so that it can query\nfor what `Did`s exist in that repository.\n\nThese two types are then used to refactor existing code, to help\nprevent the aformentioned leaking of implementation details.",
"base": "ab3c64ddf2b1fd2985c0e380039ae44f5348fa26",
"oid": "e3d54ba6efd5aec2b066778b14ad7b04d6a408f2",
"timestamp": 1778830068
},
{
"id": "45b453627fabf53216c68c346caa477bc82707c1",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Rebase",
"base": "8787796de041765ae1b53d358474079924f99494",
"oid": "5c7ab12225c7a3150fa06f27088303189c68b566",
"timestamp": 1778944794
},
{
"id": "dc5b06a0f4a1d0922ced29eceb8bf33d50f0375d",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Rebase",
"base": "fb4d84927c228d59d48afb997ff35385a2898f95",
"oid": "fca3c519c64da0c0a79ce3f81bd035ad8b8a3c9a",
"timestamp": 1780127009
},
{
"id": "f5b513ea02f488c3058daa80c5acc75148e3a189",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "REVIEW: Attempt to resolve leaky abstraction with user::Refname",
"base": "a65ac048cbda3473e7b372375d33e3a357965492",
"oid": "73b3754d75259e32c07b34ec625c3641ed885bbf",
"timestamp": 1780401375
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "50865425-b8ce-4e19-bc7c-947c733e23ad"
},
"info_url": "https://cci.rad.levitte.org//50865425-b8ce-4e19-bc7c-947c733e23ad.html"
}
Started at: 2026-09-07 18:52:31.908849+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ Remote lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz added
✓ Remote-tracking branch lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz/master created for z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz
✓ Repository successfully cloned under /opt/radcis/ci.rad.levitte.org/cci/state/50865425-b8ce-4e19-bc7c-947c733e23ad/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 189 issues · 37 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 0f07db98214142b63b138f790969f44117d4b75d
✓ Switched to branch patch/0f07db9 at revision dc5b06a
✓ Branch patch/0f07db9 setup to track rad/patches/0f07db98214142b63b138f790969f44117d4b75d
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout fca3c519c64da0c0a79ce3f81bd035ad8b8a3c9a
HEAD is now at fca3c519 radicle/git/canonical: Rename effects to objects
Exit code: 0
$ rad patch show 0f07db98214142b63b138f790969f44117d4b75d -p
╭──────────────────────────────────────────────────────────────────────────────────────╮
│ Title Introduce `git::repository::user` │
│ Patch 0f07db98214142b63b138f790969f44117d4b75d │
│ Author fintohaps z6Mkire…SQZ3voM │
│ Head fca3c519c64da0c0a79ce3f81bd035ad8b8a3c9a │
│ Base fb4d84927c228d59d48afb997ff35385a2898f95 │
│ Target master │
│ Branches patch/0f07db9 │
│ Commits ahead 25, behind 163 │
│ Status open │
│ │
│ Introduce a new module, `user`, that encapsulates logic for references │
│ in user space. │
│ │
│ The current implementation of this is having supplying `NodeId` │
│ alongside a `Qualified` reference name, to get the form │
│ `refs/namespaces/<node id>/<qualifed refname>`. │
│ The entire code base should not need to know this, yet this detail is │
│ leaked in an ad-hoc manner. │
│ On top of this, using `NodeId` creates a stronger tie to this │
│ implementation, where we know we want move towards a more flexible │
│ form, e.g. other `Did`s, in the future. │
│ │
│ The `user` module introduces two types: │
│ 1. `Namespace`: captures a user's `Did` and a generic repository, so │
│ that it can query a user's reference space. │
│ 2. `Namespaces`: captures a generic repository, so that it can query │
│ for what `Did`s exist in that repository. │
│ │
│ These two types are then used to refactor existing code, to help │
│ prevent the aformentioned leaking of implementation details. │
├──────────────────────────────────────────────────────────────────────────────────────┤
│ fca3c51 radicle/git/canonical: Rename effects to objects │
│ 175e321 radicle/canonical/effects: Migrate to user::Namespace in FindObjects │
│ d990739 radicle/git/repository/user: Namespace::find_object │
│ d26c62e radicle/storage/git: Refactor remote_ids and remotes to use user::Namespaces │
│ af27f7f git/repository/user: Add listing of DIDs │
│ 2f84deb storage/git: migrate references_of to use user::Namespace │
│ 9a0bf85 radicle/storage/refs: impl FromIterator for Refs │
│ b333442 radicle/git/repository: introduce user::Namespace │
├──────────────────────────────────────────────────────────────────────────────────────┤
│ ● Revision 0f07db9 @ ab3c64d..e3d54ba by fintohaps z6Mkire…SQZ3voM 3 months ago │
│ ↑ Revision 45b4536 @ 8787796..5c7ab12 by fintohaps z6Mkire…SQZ3voM 3 months ago │
│ ↑ Revision dc5b06a @ fb4d849..fca3c51 by fintohaps z6Mkire…SQZ3voM 3 months ago │
│ ↑ Revision f5b513e @ a65ac04..73b3754 by ade z6MkwGo…yS2aagA 3 months ago │
╰──────────────────────────────────────────────────────────────────────────────────────╯
commit fca3c519c64da0c0a79ce3f81bd035ad8b8a3c9a
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Thu Apr 30 14:38:16 2026 +0100
radicle/git/canonical: Rename effects to objects
The module no longer holds effects. Instead, it now contains a handler
for finding Git objects for a set of `Did`s.
diff --git a/crates/radicle/src/git/canonical.rs b/crates/radicle/src/git/canonical.rs
index 7b8a0559e..fb98ba735 100644
--- a/crates/radicle/src/git/canonical.rs
+++ b/crates/radicle/src/git/canonical.rs
@@ -9,7 +9,7 @@ use quorum::{CommitQuorum, CommitQuorumFailure, TagQuorum, TagQuorumFailure};
mod voting;
-pub mod effects;
+pub mod objects;
pub mod protect;
pub mod rules;
pub mod symbolic;
@@ -115,10 +115,10 @@ where
/// find the quorum.
pub fn find_objects(
self,
- ) -> Result<Canonical<'a, 'b, 'r, R, ObjectsFound>, effects::FindObjectsError> {
+ ) -> Result<Canonical<'a, 'b, 'r, R, ObjectsFound>, objects::FindObjectsError> {
let allowed: Vec<_> = self.rule.allowed().iter().copied().collect();
let FoundObjects { objects, missing } =
- effects::FindObjects::new(self.repo, &self.refname, &allowed).resolve()?;
+ objects::FindObjects::new(self.repo, &self.refname, &allowed).resolve()?;
let missing = Missing { missing };
Ok(Canonical {
refname: self.refname,
diff --git a/crates/radicle/src/git/canonical/error.rs b/crates/radicle/src/git/canonical/error.rs
index 5fb268549..50b5d8b2b 100644
--- a/crates/radicle/src/git/canonical/error.rs
+++ b/crates/radicle/src/git/canonical/error.rs
@@ -4,8 +4,8 @@ use crate::git::Oid;
use crate::git::repository::ancestry;
-use super::{ObjectType, effects};
-pub use effects::FindObjectsError;
+use super::{ObjectType, objects};
+pub use objects::FindObjectsError;
/// An error that occurred while computing a merge base.
///
diff --git a/crates/radicle/src/git/canonical/effects.rs b/crates/radicle/src/git/canonical/objects.rs
similarity index 100%
rename from crates/radicle/src/git/canonical/effects.rs
rename to crates/radicle/src/git/canonical/objects.rs
diff --git a/crates/radicle/src/storage.rs b/crates/radicle/src/storage.rs
index e36310c39..081305fd8 100644
--- a/crates/radicle/src/storage.rs
+++ b/crates/radicle/src/storage.rs
@@ -155,7 +155,7 @@ pub enum RepositoryError {
#[error("failed to get canonical reference rules: {0}")]
CanonicalRefs(#[from] doc::CanonicalRefsError),
#[error(transparent)]
- FindObjects(#[from] canonical::effects::FindObjectsError),
+ FindObjects(#[from] canonical::objects::FindObjectsError),
}
impl From<Error> for RepositoryError {
commit 175e3217b49f52d3542b9d9602c65d06a70e700a
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Thu Apr 30 08:48:43 2026 +0100
radicle/canonical/effects: Migrate to user::Namespace in FindObjects
With `Namespace::find_object`, the logic in `FindObjects::resolve` simplifies,
since it delegates to the domain.
However, this does have some knock on effects.
Note that there is an abstraction leak for keeping track of the missing references,
since the `Namespaced` reference is being constructed.
It is also no longer easy to keep track of missing objects, as well as missing references.
diff --git a/crates/radicle/src/git/canonical.rs b/crates/radicle/src/git/canonical.rs
index 322f01436..7b8a0559e 100644
--- a/crates/radicle/src/git/canonical.rs
+++ b/crates/radicle/src/git/canonical.rs
@@ -117,15 +117,9 @@ where
self,
) -> Result<Canonical<'a, 'b, 'r, R, ObjectsFound>, effects::FindObjectsError> {
let allowed: Vec<_> = self.rule.allowed().iter().copied().collect();
- let FoundObjects {
- objects,
- missing_refs,
- missing_objects,
- } = effects::FindObjects::new(self.repo, &self.refname, &allowed).resolve()?;
- let missing = Missing {
- refs: missing_refs,
- objects: missing_objects,
- };
+ let FoundObjects { objects, missing } =
+ effects::FindObjects::new(self.repo, &self.refname, &allowed).resolve()?;
+ let missing = Missing { missing };
Ok(Canonical {
refname: self.refname,
rule: self.rule,
@@ -468,25 +462,18 @@ pub struct FoundObjects {
/// The found objects, and under which [`Did`] they were found.
pub objects: BTreeMap<Did, Object>,
/// Any missing references while attempting to find the objects.
- pub missing_refs: BTreeSet<Namespaced<'static>>,
- // TODO(finto): I think this doesn't make sense now that we use only one
- // repository.
- /// Any missing objects, where the reference was found, but the object was
- /// missing.
- pub missing_objects: BTreeMap<Did, Oid>,
+ pub missing: BTreeSet<Namespaced<'static>>,
}
/// [`Missing`] marks whether there were any missing references or objects.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Missing {
- pub refs: BTreeSet<Namespaced<'static>>,
- pub objects: BTreeMap<Did, Oid>,
+ pub missing: BTreeSet<Namespaced<'static>>,
}
impl Missing {
fn found<'a>(&mut self, did: &Did, refname: &Qualified<'a>) {
- self.objects.remove(did);
- self.refs
+ self.missing
.remove(&refname.with_namespace((did.as_key()).into()).to_owned());
}
}
diff --git a/crates/radicle/src/git/canonical/effects.rs b/crates/radicle/src/git/canonical/effects.rs
index 39f8959c5..9ef993c1d 100644
--- a/crates/radicle/src/git/canonical/effects.rs
+++ b/crates/radicle/src/git/canonical/effects.rs
@@ -3,8 +3,10 @@ use std::collections::{BTreeMap, BTreeSet};
use crate::git;
use crate::git::Oid;
use crate::git::fmt::Qualified;
+use crate::git::repository;
use crate::git::repository::object;
use crate::git::repository::reference;
+use crate::git::repository::user;
use crate::prelude::Did;
use super::{FoundObjects, Object};
@@ -33,44 +35,33 @@ where
/// Resolve all references and produce the [`FoundObjects`].
pub fn resolve(self) -> Result<FoundObjects, FindObjectsError> {
let mut objects = BTreeMap::new();
- let mut missing_refs = BTreeSet::new();
- let mut missing_objects = BTreeMap::new();
+ let mut missing = BTreeSet::new();
for did in self.dids {
- let name = self.refname.with_namespace(did.as_key().into());
-
- let oid = match self.repository.ref_target(&name) {
- Ok(Some(oid)) => oid,
+ let user = user::Namespace::new(*did, self.repository);
+ match user.find_object(self.refname) {
+ Ok(Some(repository::types::Object { oid, kind })) => {
+ let object = Object::from_kind(oid, kind).ok_or_else(|| {
+ FindObjectsError::invalid_object_type(*did, oid, Some(kind.to_string()))
+ })?;
+
+ objects.insert(*did, object);
+ }
Ok(None) => {
- missing_refs.insert(name.to_owned());
+ // TODO(finto): this leaks the abstraction that we are using `refs/namespaces/<nid>`
+ let name = self.refname.with_namespace(did.as_key().into());
+ missing.insert(name.to_owned());
continue;
}
Err(e) => {
+ // TODO(finto): this leaks the abstraction that we are using `refs/namespaces/<nid>`
+ let name = self.refname.with_namespace(did.as_key().into());
return Err(FindObjectsError::find_reference(name.to_owned(), e));
}
- };
-
- let kind = match self.repository.object_kind(oid) {
- Ok(Some(kind)) => kind,
- Ok(None) => {
- missing_objects.insert(*did, oid);
- continue;
- }
- Err(e) => return Err(FindObjectsError::find_object(oid, e)),
- };
-
- let object = Object::from_kind(oid, kind).ok_or_else(|| {
- FindObjectsError::invalid_object_type(*did, oid, Some(kind.to_string()))
- })?;
-
- objects.insert(*did, object);
+ }
}
- Ok(FoundObjects {
- objects,
- missing_refs,
- missing_objects,
- })
+ Ok(FoundObjects { objects, missing })
}
}
commit d990739ade2fde235ba5698b5570bf6be5efa25e
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Thu Apr 30 13:05:20 2026 +0100
radicle/git/repository/user: Namespace::find_object
Introduce a new method `Namespace::find_object`, for finding the
object found at a `Qualified` reference in the user namespace.
diff --git a/crates/radicle/src/git/repository/types.rs b/crates/radicle/src/git/repository/types.rs
index 165ac8582..6e30d5da2 100644
--- a/crates/radicle/src/git/repository/types.rs
+++ b/crates/radicle/src/git/repository/types.rs
@@ -38,6 +38,14 @@ impl fmt::Display for ObjectKind {
}
}
+/// A resolved Git object.
+pub struct Object {
+ /// The content-addressed identifier of the object.
+ pub oid: Oid,
+ /// The kind of the object.
+ pub kind: ObjectKind,
+}
+
/// A Git blob object.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Blob {
diff --git a/crates/radicle/src/git/repository/user.rs b/crates/radicle/src/git/repository/user.rs
index 9f17285d7..b2d61d08a 100644
--- a/crates/radicle/src/git/repository/user.rs
+++ b/crates/radicle/src/git/repository/user.rs
@@ -12,14 +12,16 @@ pub mod error;
use std::collections::BTreeMap;
use crypto::PublicKey;
-use radicle_git_ref_format::{
- self as fmt, Component, Qualified, RefStr, pattern, refname, refspec,
-};
+use radicle_git_ref_format as fmt;
+use radicle_git_ref_format::{Component, Qualified, RefStr};
+use radicle_git_ref_format::{pattern, refname, refspec};
use radicle_oid::Oid;
use crate::prelude::Did;
-use super::reference;
+use super::ObjectKind;
+use super::types::Object;
+use super::{object, reference};
/// The set of references that exist for a user.
///
@@ -167,6 +169,47 @@ impl<'a, R: reference::Reader> Namespace<'a, R> {
}
}
+impl<'a, R: reference::Reader + object::Reader> Namespace<'a, R> {
+ /// Find the object that is pointed to by `refname`, in the user namespace.
+ ///
+ /// The resulting object should either be an [`ObjectKind::Commit`] or
+ /// [`ObjectKind::Tag`], but other [`ObjectKind`]'s may be returned.
+ ///
+ /// # Errors
+ ///
+ /// - [`FindObject::RefTarget`]: An error occurred when attempting to resolve the
+ /// [`Oid`] of the reference, identified by `refname`.
+ /// - [`FindObject::ObjectKind`]: An error occurred when attempting to resolve the
+ /// [`ObjectKind`] of the [`Oid`] that the reference is pointing to.
+ ///
+ /// [`FindObject::RefTarget`]: error::FindObject::RefTarget
+ /// [`FindObject::ObjectKind`]: error::FindObject::ObjectKind
+ pub fn find_object(&self, refname: &Qualified) -> Result<Option<Object>, error::FindObject> {
+ let oid = self
+ .ref_target(refname)
+ .map_err(|err| error::FindObject::RefTarget {
+ refname: refname.clone().to_owned(),
+ source: err,
+ })?;
+ oid.and_then(|oid| self.object(refname, oid).transpose())
+ .transpose()
+ }
+
+ fn object(&self, refname: &Qualified, oid: Oid) -> Result<Option<Object>, error::FindObject> {
+ self.object_kind(oid)
+ .map_err(|err| error::FindObject::ObjectKind {
+ oid,
+ refname: refname.clone().to_owned(),
+ source: err,
+ })
+ .map(|kind| kind.map(|kind| Object { oid, kind }))
+ }
+
+ fn object_kind(&self, oid: Oid) -> Result<Option<ObjectKind>, object::error::read::ObjectKind> {
+ self.repo.object_kind(oid)
+ }
+}
+
impl<'a, R: reference::Writer> Namespace<'a, R> {
/// Set a reference for this user.
///
diff --git a/crates/radicle/src/git/repository/user/error.rs b/crates/radicle/src/git/repository/user/error.rs
index 32ef2c8fb..57efe5033 100644
--- a/crates/radicle/src/git/repository/user/error.rs
+++ b/crates/radicle/src/git/repository/user/error.rs
@@ -1,4 +1,7 @@
-use crate::git::repository::reference;
+use radicle_git_ref_format::Qualified;
+use radicle_oid::Oid;
+
+use crate::git::repository::{object, reference};
/// Error returned by [`Namespace::references`].
///
@@ -11,6 +14,27 @@ pub enum References {
ListRefs(#[from] reference::error::read::ListRefs),
}
+/// Error returned by [`Namespace::find_object`]
+///
+/// [`Namespace::find_object`]: super::Namespace::find_object
+#[derive(Debug, thiserror::Error)]
+#[non_exhaustive]
+pub enum FindObject {
+ /// Failed to resolve the target of the reference.
+ #[error("failed to resolve {refname}: {source}")]
+ RefTarget {
+ refname: Qualified<'static>,
+ source: reference::error::read::RefTarget,
+ },
+ /// Failed to determine the kind of the object.
+ #[error("failed to determine object kind of {oid}, found at {refname}: {source}")]
+ ObjectKind {
+ oid: Oid,
+ refname: Qualified<'static>,
+ source: object::error::read::ObjectKind,
+ },
+}
+
/// Error returned by [`Namespaces::dids`] and [`Namespaces::dids_with_errors`].
///
/// [`Namespaces::dids`]: super::Namespaces::dids
commit d26c62e1fe159b9ea3cd694cef9a50b516196d4b
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Tue Apr 28 17:46:06 2026 +0100
radicle/storage/git: Refactor remote_ids and remotes to use user::Namespaces
The `user::Namespaces::dids` method is used to delegate finding the
set of `Did`s for `remote_ids` and `remotes`.
Important to note is that this method drops any errors while
attempting to get the `Did`, but they are logged.
This means the behaviour changes for these calls, where if a `remote`
failed, it would fail the whole function call, and now it does not.
diff --git a/crates/radicle-cli/src/commands/inspect.rs b/crates/radicle-cli/src/commands/inspect.rs
index 7f7f2e7b3..b69b747a3 100644
--- a/crates/radicle-cli/src/commands/inspect.rs
+++ b/crates/radicle-cli/src/commands/inspect.rs
@@ -64,7 +64,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
Target::Sigrefs => {
let (repo, _) = repo(rid, storage)?;
for remote in repo.remote_ids()? {
- let remote = remote?;
+ let remote = *remote.as_key();
let refs = RefsAt::new(&repo, remote)?;
let sigrefs = SignedRefs::load_at(refs.at, remote, &repo);
diff --git a/crates/radicle-cli/src/commands/remote/list.rs b/crates/radicle-cli/src/commands/remote/list.rs
index 1a4e997f6..a2fd9d835 100644
--- a/crates/radicle-cli/src/commands/remote/list.rs
+++ b/crates/radicle-cli/src/commands/remote/list.rs
@@ -68,16 +68,12 @@ pub fn untracked<'a>(
.collect::<HashSet<_>>();
Ok(remotes
.filter_map(|remote| {
- remote
- .map(|remote| {
- (!git_remotes.contains(&remote)).then_some(Untracked {
- remote,
- alias: aliases.alias(&remote),
- })
- })
- .transpose()
+ (!git_remotes.contains(&remote)).then_some(Untracked {
+ remote: *remote.as_key(),
+ alias: aliases.alias(&remote),
+ })
})
- .collect::<Result<Vec<_>, _>>()?)
+ .collect::<Vec<_>>())
}
pub fn print_tracked<'a>(tracked: impl Iterator<Item = &'a Tracked>) {
diff --git a/crates/radicle-cli/src/commands/stats.rs b/crates/radicle-cli/src/commands/stats.rs
index 0465c6012..d9f3daa77 100644
--- a/crates/radicle-cli/src/commands/stats.rs
+++ b/crates/radicle-cli/src/commands/stats.rs
@@ -67,7 +67,6 @@ pub fn run(_args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
stats.local.repos += 1;
for remote in repo.remote_ids()? {
- let remote = remote?;
let sigrefs = repo.reference_oid(&remote, &git::refs::storage::SIGREFS_BRANCH)?;
let mut walk = repo.raw().revwalk()?;
walk.push(sigrefs.into())?;
diff --git a/crates/radicle-cli/tests/commands/id.rs b/crates/radicle-cli/tests/commands/id.rs
index 24f0e8c27..a80129d18 100644
--- a/crates/radicle-cli/tests/commands/id.rs
+++ b/crates/radicle-cli/tests/commands/id.rs
@@ -358,8 +358,8 @@ fn rad_id_collaboration() {
let mut remotes = repo
.remote_ids()
.unwrap()
- .collect::<Result<Vec<_>, _>>()
- .unwrap();
+ .map(|did| *did.as_key())
+ .collect::<Vec<_>>();
let mut expected = vec![alice.id, bob.id, eve.id];
remotes.sort();
expected.sort();
@@ -369,8 +369,8 @@ fn rad_id_collaboration() {
let mut remotes = repo
.remote_ids()
.unwrap()
- .collect::<Result<Vec<_>, _>>()
- .unwrap();
+ .map(|did| *did.as_key())
+ .collect::<Vec<_>>();
let mut expected = vec![alice.id, bob.id, eve.id];
remotes.sort();
expected.sort();
diff --git a/crates/radicle-fetch/src/state.rs b/crates/radicle-fetch/src/state.rs
index bcec3c0d8..6f16fc4d8 100644
--- a/crates/radicle-fetch/src/state.rs
+++ b/crates/radicle-fetch/src/state.rs
@@ -71,7 +71,7 @@ pub mod error {
#[error(transparent)]
RemoteRefs(#[from] sigrefs::error::RemoteRefs),
#[error("failed to get remote namespaces: {0}")]
- RemoteIds(#[source] radicle::git::raw::Error),
+ RemoteIds(#[source] radicle::storage::refs::Error),
#[error(transparent)]
Step(#[from] Step),
#[error(transparent)]
@@ -465,7 +465,7 @@ impl FetchState {
.repository()
.remote_ids()
.map_err(error::Protocol::RemoteIds)?
- .filter_map(|id| id.ok())
+ .map(|did| *did.as_key())
.filter(|id| delegates.contains(id))
.collect::<BTreeSet<_>>();
let mut failed_delegates = BTreeSet::new();
diff --git a/crates/radicle-node/src/tests/e2e.rs b/crates/radicle-node/src/tests/e2e.rs
index 6ab9579d5..1a0b2d7bc 100644
--- a/crates/radicle-node/src/tests/e2e.rs
+++ b/crates/radicle-node/src/tests/e2e.rs
@@ -343,7 +343,7 @@ fn test_replication_invalid() {
let repo = alice.storage.repository(acme).unwrap();
let mut remotes = repo.remote_ids().unwrap();
- assert_eq!(remotes.next().unwrap().unwrap(), bob.id);
+ assert_eq!(*remotes.next().unwrap().as_key(), bob.id);
assert!(remotes.next().is_none());
assert!(repo.validate().unwrap().is_empty());
@@ -485,8 +485,8 @@ fn test_fetch_followed_remotes() {
let bob_remotes = bob_repo
.remote_ids()
.unwrap()
- .collect::<Result<HashSet<_>, _>>()
- .unwrap();
+ .map(|did| *did.as_key())
+ .collect::<HashSet<_>>();
assert_eq!(bob_remotes.len(), followed.len() + 1);
assert!(bob_remotes.is_superset(&followed));
diff --git a/crates/radicle/src/rad.rs b/crates/radicle/src/rad.rs
index 7af413b23..799bfa51f 100644
--- a/crates/radicle/src/rad.rs
+++ b/crates/radicle/src/rad.rs
@@ -549,8 +549,8 @@ mod tests {
.unwrap()
.remotes()
.unwrap()
- .collect::<Result<_, _>>()
- .unwrap();
+ .into_iter()
+ .collect();
let project_repo = storage.repository(proj).unwrap();
let (_, head) = project_repo.head().unwrap();
diff --git a/crates/radicle/src/storage/git.rs b/crates/radicle/src/storage/git.rs
index 28a389d19..995b1a8c8 100644
--- a/crates/radicle/src/storage/git.rs
+++ b/crates/radicle/src/storage/git.rs
@@ -209,7 +209,7 @@ impl WriteStorage for Storage {
if has_sigrefs {
repo.clean(&self.info.key)
} else {
- let remotes = repo.remote_ids()?.collect::<Result<_, _>>()?;
+ let remotes = repo.remote_ids()?.map(|did| *did.as_key()).collect();
repo.remove()?;
Ok(remotes)
}
@@ -451,14 +451,7 @@ impl Repository {
.collect::<BTreeSet<_>>();
let mut deleted = Vec::new();
for id in self.remote_ids()? {
- let id = match id {
- Ok(id) => id,
- Err(e) => {
- log::error!(target: "storage", "Failed to clean up remote: {e}");
- continue;
- }
- };
-
+ let id = *id.as_key();
// N.b. it is fatal to delete local or delegates
if *local == id || delegates.contains(&id) {
continue;
@@ -558,44 +551,24 @@ impl Repository {
pub fn remote_ids(
&self,
- ) -> Result<impl Iterator<Item = Result<RemoteId, refs::Error>> + '_, git::raw::Error> {
- let iter = self.backend.references_glob(SIGREFS_GLOB.as_str())?.map(
- |reference| -> Result<RemoteId, refs::Error> {
- let r = reference?;
- let name = r.name().ok_or(refs::Error::InvalidRef)?;
- let (id, _) = git::parse_ref_namespaced::<RemoteId>(name)?;
-
- Ok(id)
- },
- );
- Ok(iter)
+ ) -> Result<user::Dids<<Self as reference::Reader>::References<'_>>, refs::Error> {
+ let namespaces = user::Namespaces::new(self);
+ Ok(namespaces.dids(user::FilterBy::suffix(&git::fmt::refname!("rad/sigrefs")))?)
}
- pub fn remotes(
- &self,
- ) -> Result<impl Iterator<Item = Result<(RemoteId, Remote), refs::Error>> + '_, git::raw::Error>
- {
- let remotes =
- self.backend
- .references_glob(SIGREFS_GLOB.as_str())?
- .map(|reference| -> Result<_, _> {
- let r = reference?;
- let name = r.name().ok_or(refs::Error::InvalidRef)?;
- let (id, _) = git::parse_ref_namespaced::<RemoteId>(name)?;
- let remote = self.remote(&id)?;
-
- Ok((id, remote))
- });
+ pub fn remotes(&self) -> Result<Vec<(RemoteId, Remote)>, refs::Error> {
+ let mut remotes = Vec::new();
+ for id in self.remote_ids()? {
+ let remote = self.remote(&id)?;
+ remotes.push((*id.as_key(), remote));
+ }
Ok(remotes)
}
}
impl RemoteRepository for Repository {
fn remotes(&self) -> Result<Remotes, refs::Error> {
- let mut remotes = Vec::new();
- for remote in Repository::remotes(self)? {
- remotes.push(remote?);
- }
+ let remotes = Repository::remotes(self)?;
Ok(Remotes::from_iter(remotes))
}
@@ -613,8 +586,7 @@ impl RemoteRepository for Repository {
let mut all = Vec::new();
for remote in self.remote_ids()? {
- let remote = remote?;
- let refs_at = RefsAt::new(self, remote)?;
+ let refs_at = RefsAt::new(self, *remote)?;
all.push(refs_at);
}
@@ -676,7 +648,11 @@ impl ReadRepository for Repository {
}
fn is_empty(&self) -> Result<bool, git::raw::Error> {
- Ok(self.remotes()?.next().is_none())
+ Ok(self
+ .backend
+ .references_glob(SIGREFS_GLOB.as_str())?
+ .next()
+ .is_none())
}
fn path(&self) -> &Path {
@@ -876,7 +852,6 @@ impl ReadRepository for Repository {
fn canonical_identity_head(&self) -> Result<Oid, RepositoryError> {
for remote in self.remote_ids()? {
- let remote = remote?;
// Nb. A remote may not have an identity document if the user has not contributed
// any changes to the identity COB.
let Ok(root) = self.identity_root_of(&remote) else {
diff --git a/crates/radicle/src/storage/refs.rs b/crates/radicle/src/storage/refs.rs
index b0164ab05..dbeb3d82a 100644
--- a/crates/radicle/src/storage/refs.rs
+++ b/crates/radicle/src/storage/refs.rs
@@ -44,6 +44,8 @@ pub enum Error {
#[error(transparent)]
UserReferences(#[from] git::repository::user::error::References),
#[error(transparent)]
+ Dids(#[from] git::repository::user::error::Dids),
+ #[error(transparent)]
Read(#[from] sigrefs::read::error::Read),
#[error(transparent)]
Write(#[from] sigrefs::write::error::Write),
commit af27f7fbcc9cbb0224f3b25222662fcbc3a84590
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Tue Apr 28 17:46:06 2026 +0100
git/repository/user: Add listing of DIDs
Add `user::Namespaces` that provides a way to discover the DIDs that
are associated with the repository through references.
Since the current implementation uses `refs/namespaces` listing all
the references provides multiple of the same `Did`.
This can be managed by providing a filter for a well-known, unique
referece, i.e. `rad/sigrefs`; thus, the API supports a filter.
diff --git a/crates/radicle/src/git/repository/user.rs b/crates/radicle/src/git/repository/user.rs
index 3cc921320..9f17285d7 100644
--- a/crates/radicle/src/git/repository/user.rs
+++ b/crates/radicle/src/git/repository/user.rs
@@ -11,7 +11,10 @@ pub mod error;
use std::collections::BTreeMap;
-use radicle_git_ref_format::{self as fmt, Component, Qualified, refname, refspec};
+use crypto::PublicKey;
+use radicle_git_ref_format::{
+ self as fmt, Component, Qualified, RefStr, pattern, refname, refspec,
+};
use radicle_oid::Oid;
use crate::prelude::Did;
@@ -190,3 +193,177 @@ impl<'a, R: reference::Writer> Namespace<'a, R> {
self.repo.delete_ref(&self.namespaced(name))
}
}
+
+/// Discovery of users (namespaces) in a Git repository.
+///
+/// [`Namespaces`] provides iterator-based access to the [`Did`]s that have
+/// references in the repository. The optional `filter_by` suffix narrows the
+/// search — for example, passing `refs/rad/sigrefs` limits discovery to
+/// users that have a signed-refs branch.
+pub struct Namespaces<'a, R> {
+ repo: &'a R,
+}
+
+/// Provide a filter for [`Namespaces::dids`] and
+/// [`Namespaces::dids_with_errors`].
+pub enum FilterBy<'a> {
+ /// Provide a suffix to filter the [`Did`]s by.
+ Suffix(&'a RefStr),
+ /// No filter is provided, returning all [`Did`]s.
+ Empty,
+}
+
+impl<'a> FilterBy<'a> {
+ /// Constructs a [`FilterBy::Suffix`].
+ pub fn suffix<R>(suffix: &'a R) -> Self
+ where
+ R: AsRef<RefStr>,
+ {
+ Self::Suffix(suffix.as_ref())
+ }
+
+ /// Constructs a [`FilterBy::Empty`].
+ pub fn empty() -> Self {
+ Self::Empty
+ }
+}
+
+impl<'a, R> Namespaces<'a, R>
+where
+ R: reference::Reader,
+{
+ /// Create a new [`Namespaces`] handle backed by `repo`.
+ pub fn new(repo: &'a R) -> Self {
+ Self { repo }
+ }
+
+ /// Iterate over discovered [`Did`]s, logging and skipping errors.
+ ///
+ /// When `filter_by` is [`Empty`], all namespaces are returned. When a [`Suffix`]
+ /// is provided (e.g. `refs/rad/sigrefs`), only namespaces containing a
+ /// reference matching that suffix are returned.
+ ///
+ /// **Note**: the returned [`Did`]s may contain duplicates when
+ /// `filter_by` is [`Empty`], since a single namespace can contain multiple
+ /// references.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the reference iterator cannot be initialised.
+ ///
+ /// [`Empty`]: FilterBy::Empty
+ /// [`Suffix`]: FilterBy::Suffix
+ pub fn dids(self, filter_by: FilterBy<'_>) -> Result<Dids<R::References<'a>>, error::Dids> {
+ let inner = self.refs_iter(filter_by)?;
+ Ok(Dids { inner })
+ }
+
+ /// Like [`Self::dids`], but yields `Result<Did, NamespaceError>` so the
+ /// caller can handle per-reference failures.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the reference iterator cannot be initialised.
+ pub fn dids_with_errors(
+ self,
+ filter_by: FilterBy<'_>,
+ ) -> Result<DidsWithErrors<R::References<'a>>, error::Dids> {
+ let inner = self.refs_iter(filter_by)?;
+ Ok(DidsWithErrors { inner })
+ }
+
+ fn refs_iter(
+ &self,
+ filter_by: FilterBy<'_>,
+ ) -> Result<R::References<'a>, reference::error::read::ListRefs> {
+ let pattern = pattern!("refs/namespaces/*");
+ let pattern = match filter_by {
+ FilterBy::Suffix(suffix) => pattern.join(suffix),
+ FilterBy::Empty => pattern,
+ };
+ self.repo.list_refs(&pattern)
+ }
+}
+
+/// Extract a [`Did`] from a namespaced [`Qualified`] reference name.
+///
+/// Returns `None` if the reference is not namespaced.
+fn to_did(refname: &Qualified<'_>) -> Option<Result<Did, crypto::PublicKeyError>> {
+ let namespaced = refname.to_namespaced()?;
+ let did = namespaced
+ .namespace()
+ .as_str()
+ .parse::<PublicKey>()
+ .map(Did::from);
+ Some(did)
+}
+
+/// Iterator yielding [`Did`]s, logging and skipping errors.
+///
+/// Produced by [`Namespaces::dids`].
+pub struct Dids<I> {
+ inner: I,
+}
+
+impl<I> Iterator for Dids<I>
+where
+ I: Iterator<Item = Result<(Qualified<'static>, Oid), reference::error::read::ListReference>>,
+{
+ type Item = Did;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ match self.inner.next()? {
+ Ok((name, _)) => match to_did(&name) {
+ Some(Ok(did)) => return Some(did),
+ Some(Err(e)) => {
+ log::warn!(target: "radicle", "Skipping namespace with invalid key: {e}");
+ }
+ None => {}
+ },
+ Err(e) => {
+ log::warn!(target: "radicle", "Skipping malformed reference: {e}");
+ }
+ }
+ }
+ }
+}
+
+/// Error produced by [`DidsWithErrors`].
+#[derive(Debug, thiserror::Error)]
+#[non_exhaustive]
+pub enum NamespaceError {
+ /// The namespace component could not be parsed as a [`Did`].
+ #[error("invalid namespace key: {0}")]
+ Did(#[from] crypto::PublicKeyError),
+ /// A reference could not be read or resolved.
+ #[error(transparent)]
+ Reference(#[from] reference::error::read::ListReference),
+}
+
+/// Iterator yielding `Result<Did, NamespaceError>`.
+///
+/// Produced by [`Namespaces::dids_with_errors`].
+pub struct DidsWithErrors<I> {
+ inner: I,
+}
+
+impl<I> Iterator for DidsWithErrors<I>
+where
+ I: Iterator<Item = Result<(Qualified<'static>, Oid), reference::error::read::ListReference>>,
+{
+ type Item = Result<Did, NamespaceError>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ match self.inner.next()? {
+ Ok((name, _)) => match to_did(&name) {
+ Some(Ok(did)) => return Some(Ok(did)),
+ Some(Err(e)) => return Some(Err(NamespaceError::Did(e))),
+ None => continue,
+ },
+ Err(e) => return Some(Err(NamespaceError::Reference(e))),
+ }
+ }
+ }
+}
diff --git a/crates/radicle/src/git/repository/user/error.rs b/crates/radicle/src/git/repository/user/error.rs
index c794ab90e..32ef2c8fb 100644
--- a/crates/radicle/src/git/repository/user/error.rs
+++ b/crates/radicle/src/git/repository/user/error.rs
@@ -10,3 +10,15 @@ pub enum References {
#[error(transparent)]
ListRefs(#[from] reference::error::read::ListRefs),
}
+
+/// Error returned by [`Namespaces::dids`] and [`Namespaces::dids_with_errors`].
+///
+/// [`Namespaces::dids`]: super::Namespaces::dids
+/// [`Namespaces::dids_with_errors`]: super::Namespaces::dids_with_errors
+#[derive(Debug, thiserror::Error)]
+#[non_exhaustive]
+pub enum Dids {
+ /// Failed to initialise the reference iterator.
+ #[error(transparent)]
+ ListRefs(#[from] reference::error::read::ListRefs),
+}
commit 2f84deba9aa1d1b7aeaf1efba35616592d51df47
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Tue Apr 21 17:23:07 2026 +0100
storage/git: migrate references_of to use user::Namespace
The method `references_of` is refactored to use the `user::Namespace`
mechanism for listing a set of references for a given `Did`.
In this case, the pattern is `refs/*`, i.e. all references.
diff --git a/crates/radicle/src/storage/git.rs b/crates/radicle/src/storage/git.rs
index d85ed2d38..28a389d19 100644
--- a/crates/radicle/src/storage/git.rs
+++ b/crates/radicle/src/storage/git.rs
@@ -13,8 +13,9 @@ use std::{fs, io};
use crate::git::canonical::Quorum;
use crate::git::raw::ErrorExt as _;
-use crate::git::repository;
+use crate::git::repository::{self, user};
use crate::git::repository::{ancestry, object, reference, revwalk};
+use crate::identity::Did;
use crate::identity::doc::DocError;
use crate::identity::{Doc, DocAt, RepoId};
use crate::identity::{Identity, Project};
@@ -758,26 +759,22 @@ impl ReadRepository for Repository {
///
/// [`staging::patch`]: crate::git::refs::storage::staging::patch
fn references_of(&self, remote: &RemoteId) -> Result<Refs, Error> {
- let entries = self
- .backend
- .references_glob(format!("refs/namespaces/{remote}/*").as_str())?;
-
- let mut refs = Refs::new();
+ let did = Did::from(*remote);
+ let user = user::Namespace::new(did, self);
+ let all = user
+ .references(&git::fmt::pattern!("refs/*"))
+ .map_err(|e| Error::from(refs::Error::from(e)))?;
- for e in entries {
- let e = e?;
- let name = e.name().ok_or(Error::InvalidRef)?;
- let (_, refname) = git::parse_ref::<RemoteId>(name)?;
- let oid = e.resolve()?.target().ok_or(Error::InvalidRef)?;
- let (_, category, subcategory, _) = refname.non_empty_components();
-
- match (category.as_str(), subcategory.as_str()) {
- ("tmp", "heads") => continue,
- _ => {
- refs.insert(refname.into(), oid.into());
+ let refs = all
+ .into_iter()
+ .filter_map(|(refname, oid)| {
+ let (_, category, subcategory, _) = refname.non_empty_components();
+ match (category.as_str(), subcategory.as_str()) {
+ ("tmp", "heads") => None,
+ _ => Some((refname.to_ref_string(), oid)),
}
- }
- }
+ })
+ .collect::<Refs>();
Ok(refs)
}
diff --git a/crates/radicle/src/storage/refs.rs b/crates/radicle/src/storage/refs.rs
index 730ec6b9d..b0164ab05 100644
--- a/crates/radicle/src/storage/refs.rs
+++ b/crates/radicle/src/storage/refs.rs
@@ -42,6 +42,8 @@ pub enum Error {
#[error(transparent)]
Git(#[from] git::raw::Error),
#[error(transparent)]
+ UserReferences(#[from] git::repository::user::error::References),
+ #[error(transparent)]
Read(#[from] sigrefs::read::error::Read),
#[error(transparent)]
Write(#[from] sigrefs::write::error::Write),
commit 9a0bf85dfcb17625cfec0b564d5091e26a04982a
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Tue Apr 28 17:29:02 2026 +0100
radicle/storage/refs: impl FromIterator for Refs
Implement the more common form of `FromIterator` for the `Refs` type.
Its idiosyncratic `impl From<I> where I: Iterator` is now implemented
using `collect`.
diff --git a/crates/radicle/src/storage/refs.rs b/crates/radicle/src/storage/refs.rs
index cec31f072..730ec6b9d 100644
--- a/crates/radicle/src/storage/refs.rs
+++ b/crates/radicle/src/storage/refs.rs
@@ -252,6 +252,16 @@ impl IntoIterator for Refs {
}
}
+impl FromIterator<(git::fmt::RefString, Oid)> for Refs {
+ fn from_iter<T: IntoIterator<Item = (git::fmt::RefString, Oid)>>(iter: T) -> Self {
+ iter.into_iter()
+ .fold(Refs::new(), |mut refs, (refname, target)| {
+ refs.insert(refname, target);
+ refs
+ })
+ }
+}
+
impl From<Refs> for BTreeMap<git::fmt::RefString, Oid> {
fn from(refs: Refs) -> Self {
refs.0
@@ -263,11 +273,7 @@ where
I: Iterator<Item = (git::fmt::RefString, Oid)>,
{
fn from(value: I) -> Self {
- let mut refs = Self::new();
- for (refname, target) in value {
- refs.insert(refname, target);
- }
- refs
+ value.into_iter().collect()
}
}
commit b3334426dc9ee4d1432dc78fad681b81f5bd87e4
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Tue Apr 14 15:51:19 2026 +0000
radicle/git/repository: introduce user::Namespace
Introduce a new module under `repository`, called `user`.
The module holds a `Namespace` struct that encapsulates scoping
repository queries to a single given user.
The approach opts to use `Did` to move away from assuming that user's
space is just a single `NodeId` namespace.
For now, the `Did` is a 1-to-1 mapping to `NodeId`, and thus maps to a
single namespace. The hope is that in the future, the `Did` type can
capture an evolvable identity, that could map differently to how to
query a user's references. This API would then, roughly, stay the same.
diff --git a/crates/radicle/src/git/repository.rs b/crates/radicle/src/git/repository.rs
index 73423a00f..993172078 100644
--- a/crates/radicle/src/git/repository.rs
+++ b/crates/radicle/src/git/repository.rs
@@ -16,6 +16,7 @@ pub mod object;
pub mod reference;
pub mod revwalk;
pub mod types;
+pub mod user;
mod adapter;
diff --git a/crates/radicle/src/git/repository/user.rs b/crates/radicle/src/git/repository/user.rs
new file mode 100644
index 000000000..3cc921320
--- /dev/null
+++ b/crates/radicle/src/git/repository/user.rs
@@ -0,0 +1,192 @@
+//! User-scoped Git reference access.
+//!
+//! [`Namespace`] provides read and write access to a single user's references
+//! within a Git repository. Consumers work with [`Qualified`] names (e.g.
+//! `refs/heads/main`); the namespace mapping (`refs/namespaces/<key>/…`) is
+//! handled internally.
+//!
+//! [`Qualified`]: radicle_git_ref_format::Qualified
+
+pub mod error;
+
+use std::collections::BTreeMap;
+
+use radicle_git_ref_format::{self as fmt, Component, Qualified, refname, refspec};
+use radicle_oid::Oid;
+
+use crate::prelude::Did;
+
+use super::reference;
+
+/// The set of references that exist for a user.
+///
+/// See [`Namespace::references`].
+pub struct References {
+ inner: BTreeMap<Qualified<'static>, Oid>,
+}
+
+impl References {
+ fn new() -> Self {
+ Self {
+ inner: BTreeMap::new(),
+ }
+ }
+
+ fn insert(&mut self, refname: Qualified<'static>, oid: Oid) {
+ self.inner.insert(refname, oid);
+ }
+}
+
+impl References {
+ /// Get the target [`Oid`] of the given `refname`, if it exists.
+ pub fn target_of(&self, refname: &Qualified<'static>) -> Option<&Oid> {
+ self.inner.get(refname)
+ }
+}
+
+impl<'a> IntoIterator for &'a References {
+ type Item = (&'a Qualified<'static>, &'a Oid);
+ type IntoIter = std::collections::btree_map::Iter<'a, Qualified<'static>, Oid>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.inner.iter()
+ }
+}
+
+impl IntoIterator for References {
+ type Item = (Qualified<'static>, Oid);
+ type IntoIter = std::collections::btree_map::IntoIter<Qualified<'static>, Oid>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.inner.into_iter()
+ }
+}
+
+/// User-scoped reference handle.
+///
+/// Wraps a repository `R` and a [`Did`], translating [`Qualified`] reference
+/// names into their namespaced physical location.
+pub struct Namespace<'a, R> {
+ did: Did,
+ repo: &'a R,
+}
+
+impl<'a, R> Namespace<'a, R> {
+ /// Create a new [`Namespace`] for `did` backed by `repo`.
+ pub fn new(did: Did, repo: &'a R) -> Self {
+ Self { did, repo }
+ }
+
+ /// The [`Did`] this handle is scoped to.
+ pub fn did(&self) -> Did {
+ self.did
+ }
+
+ /// Map a [`Qualified`] reference to its namespaced form.
+ fn namespaced<'b>(&self, name: &Qualified<'b>) -> fmt::Namespaced<'b> {
+ name.with_namespace(fmt::Component::from(self.did.as_key()))
+ }
+}
+
+impl<'a, R: reference::Reader> Namespace<'a, R> {
+ /// Resolve a reference to its target [`Oid`].
+ ///
+ /// Returns `None` if the reference does not exist for this user.
+ ///
+ /// # Errors
+ ///
+ /// - [`Backend`]: An unexpected error from the underlying git library.
+ ///
+ /// [`Backend`]: reference::error::read::RefTarget::Backend
+ pub fn ref_target(
+ &self,
+ name: &Qualified,
+ ) -> Result<Option<Oid>, reference::error::read::RefTarget> {
+ self.repo.ref_target(&self.namespaced(name))
+ }
+
+ /// Resolve a reference to its target [`Oid`], returning an error if it does
+ /// not exist.
+ ///
+ /// # Errors
+ ///
+ /// - [`NotFound`]: The reference does not exist for this user.
+ /// - [`Backend`]: An unexpected error from the underlying git library.
+ ///
+ /// [`NotFound`]: reference::error::read::RefTarget::NotFound
+ /// [`Backend`]: reference::error::read::RefTarget::Backend
+ pub fn try_ref_target(
+ &self,
+ name: &Qualified,
+ ) -> Result<Oid, reference::error::read::RefTarget> {
+ self.repo.try_ref_target(&self.namespaced(name))
+ }
+
+ /// List all references for this user matching a glob pattern.
+ ///
+ /// The `pattern` is relative to the user's namespace. For example,
+ /// `refs/*` matches all references, and `refs/heads/*` matches only
+ /// branches.
+ ///
+ /// Each returned [`Qualified`] has the namespace stripped — callers see
+ /// `refs/heads/main`, not `refs/namespaces/<key>/refs/heads/main`.
+ ///
+ /// Per-reference failures (parse or peel errors) are logged and skipped.
+ ///
+ /// # Errors
+ ///
+ /// - [`ListRefs`]: An unexpected error when initialising the iterator.
+ ///
+ /// [`ListRefs`]: error::References::ListRefs
+ pub fn references(
+ &self,
+ pattern: &refspec::PatternStr,
+ ) -> Result<References, error::References> {
+ let namespaced = refname!("refs/namespaces")
+ .join(Component::from(self.did.as_key()))
+ .to_pattern(pattern);
+
+ let refs = self.repo.list_refs(&namespaced)?;
+ let references = refs.fold(References::new(), |mut refs, entry| {
+ match entry {
+ Ok((name, oid)) => {
+ if let Some(ns) = name.to_namespaced() {
+ refs.insert(ns.strip_namespace(), oid);
+ }
+ }
+ Err(e) => {
+ log::warn!("Skipping reference: {e}");
+ }
+ }
+ refs
+ });
+ Ok(references)
+ }
+}
+
+impl<'a, R: reference::Writer> Namespace<'a, R> {
+ /// Set a reference for this user.
+ ///
+ /// # Errors
+ ///
+ /// See [`reference::Writer::write_ref`] for error details.
+ pub fn write_ref(
+ &self,
+ name: &Qualified,
+ target: reference::Target,
+ reflog: &str,
+ ) -> Result<(), reference::error::write::WriteRef> {
+ self.repo.write_ref(&self.namespaced(name), target, reflog)
+ }
+
+ /// Delete a reference for this user.
+ ///
+ /// This operation is idempotent.
+ ///
+ /// # Errors
+ ///
+ /// See [`reference::Writer::delete_ref`] for error details.
+ pub fn delete_ref(&self, name: &Qualified) -> Result<(), reference::error::write::DeleteRef> {
+ self.repo.delete_ref(&self.namespaced(name))
+ }
+}
diff --git a/crates/radicle/src/git/repository/user/error.rs b/crates/radicle/src/git/repository/user/error.rs
new file mode 100644
index 000000000..c794ab90e
--- /dev/null
+++ b/crates/radicle/src/git/repository/user/error.rs
@@ -0,0 +1,12 @@
+use crate::git::repository::reference;
+
+/// Error returned by [`Namespace::references`].
+///
+/// [`Namespace::references`]: super::Namespace::references
+#[derive(Debug, thiserror::Error)]
+#[non_exhaustive]
+pub enum References {
+ /// Failed to initialise the reference iterator.
+ #[error(transparent)]
+ ListRefs(#[from] reference::error::read::ListRefs),
+}
Exit code: 0
shell: 'export RUSTDOCFLAGS=''-D warnings'' cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny warnings cargo build --all-targets --workspace cargo doc --workspace --no-deps --all-features cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name 50865425-b8ce-4e19-bc7c-947c733e23ad -v /opt/radcis/ci.rad.levitte.org/cci/state/50865425-b8ce-4e19-bc7c-947c733e23ad/s:/50865425-b8ce-4e19-bc7c-947c733e23ad/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/50865425-b8ce-4e19-bc7c-947c733e23ad/w:/50865425-b8ce-4e19-bc7c-947c733e23ad/w -w /50865425-b8ce-4e19-bc7c-947c733e23ad/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:trixie bash /50865425-b8ce-4e19-bc7c-947c733e23ad/s/script.sh
+ export 'RUSTDOCFLAGS=-D warnings'
+ RUSTDOCFLAGS='-D warnings'
+ cargo --version
info: syncing channel updates for '1.95-x86_64-unknown-linux-gnu'
info: latest update on 2026-04-16, rust version 1.95.0 (59807616e 2026-04-14)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-src'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
info: installing component 'rust-src'
info: installing component 'rust-std'
info: installing component 'rustc'
info: installing component 'rustfmt'
cargo 1.95.0 (f2d3ce0bd 2026-03-21)
+ rustc --version
rustc 1.95.0 (59807616e 2026-04-14)
+ cargo fmt --check
+ cargo clippy --all-targets --workspace -- --deny warnings
Updating crates.io index
Downloading crates ...
Downloaded amplify v4.9.0
Downloaded amplify_derive v4.0.1
Downloaded ahash v0.8.12
Downloaded aes-gcm v0.10.3
Downloaded errno v0.3.14
Downloaded form_urlencoded v1.2.2
Downloaded gix-shallow v0.12.0
Downloaded filetime v0.2.27
Downloaded fancy-regex v0.14.0
Downloaded ed25519 v2.2.3
Downloaded find-msvc-tools v0.1.9
Downloaded equivalent v1.0.2
Downloaded email_address v0.2.9
Downloaded curve25519-dalek-derive v0.1.1
Downloaded env_logger v0.11.9
Downloaded digest v0.10.7
Downloaded num v0.4.3
Downloaded gix-utils v0.3.2
Downloaded match-lookup v0.1.2
Downloaded itoa v1.0.17
Downloaded gix-diff v0.63.0
Downloaded litrs v1.0.0
Downloaded lexopt v0.3.2
Downloaded keccak v0.1.6
Downloaded idna_adapter v1.2.1
Downloaded gix-chunk v0.7.1
Downloaded heapless v0.8.0
Downloaded memchr v2.8.0
Downloaded normalize-line-endings v0.3.0
Downloaded ec25519 v0.1.0
Downloaded opaque-debug v0.3.1
Downloaded outref v0.5.2
Downloaded lazy_static v1.5.0
Downloaded percent-encoding v2.3.2
Downloaded log v0.4.29
Downloaded num-rational v0.4.2
Downloaded git-ref-format-macro v0.6.0
Downloaded nu-ansi-term v0.50.3
Downloaded group v0.13.0
Downloaded ref-cast-impl v1.0.25
Downloaded once_cell v1.21.4
Downloaded rustc_version v0.4.1
Downloaded radicle-std-ext v0.2.0
Downloaded num-traits v0.2.19
Downloaded same-file v1.0.6
Downloaded rustc-demangle v0.1.27
Downloaded qcheck v1.0.0
Downloaded rand_core v0.9.5
Downloaded scopeguard v1.2.0
Downloaded rand_core v0.6.4
Downloaded scrypt v0.11.0
Downloaded sha1 v0.10.6
Downloaded secrecy v0.10.3
Downloaded ssh-encoding v0.2.0
Downloaded serde_spanned v1.0.4
Downloaded siphasher v1.0.2
Downloaded schemars_derive v1.2.1
Downloaded serde-untagged v0.1.9
Downloaded sval_json v2.17.0
Downloaded sval_serde v2.17.0
Downloaded quote v1.0.45
Downloaded siphasher v0.3.11
Downloaded referencing v0.30.0
Downloaded test-log-macros v0.2.19
Downloaded proc-macro2 v1.0.106
Downloaded test-log v0.2.19
Downloaded snapbox v0.4.17
Downloaded serde_core v1.0.228
Downloaded stable_deref_trait v1.2.1
Downloaded ssh-agent-lib v0.6.0
Downloaded signal-hook-registry v1.4.8
Downloaded sval_ref v2.17.0
Downloaded icu_properties_data v2.1.2
Downloaded spki v0.7.3
Downloaded thread_local v1.1.9
Downloaded tinyvec_macros v0.1.1
Downloaded thiserror v2.0.18
Downloaded tinyvec v1.11.0
Downloaded signature v2.2.0
Downloaded unit-prefix v0.5.2
Downloaded tracing-core v0.1.36
Downloaded uuid-simd v0.8.0
Downloaded value-bag-sval2 v1.12.0
Downloaded value-bag-serde1 v1.12.0
Downloaded sysinfo v0.37.2
Downloaded zerofrom v0.1.6
Downloaded yoke-derive v0.8.1
Downloaded yoke v0.8.1
Downloaded uuid v1.22.0
Downloaded toml v0.9.12+spec-1.1.0
Downloaded utf8_iter v1.0.4
Downloaded tinystr v0.8.2
Downloaded spin v0.9.8
Downloaded tree-sitter-toml-ng v0.6.0
Downloaded tree-sitter-highlight v0.24.7
Downloaded streaming-iterator v0.1.9
Downloaded zerovec-derive v0.11.2
Downloaded zerofrom-derive v0.1.6
Downloaded unicode-normalization v0.1.25
Downloaded tempfile v3.27.0
Downloaded tree-sitter-html v0.23.2
Downloaded zeroize v1.8.2
Downloaded timeago v0.4.2
Downloaded writeable v0.6.2
Downloaded zerovec v0.11.5
Downloaded url v2.5.8
Downloaded signal-hook v0.3.18
Downloaded serde v1.0.228
Downloaded zmij v1.0.21
Downloaded bstr v1.12.1
Downloaded value-bag v1.12.0
Downloaded vcpkg v0.2.15
Downloaded zerocopy v0.8.42
Downloaded zerotrie v0.2.3
Downloaded unicode-segmentation v1.12.0
Downloaded portable-atomic v1.13.1
Downloaded bloomy v1.2.0
Downloaded tree-sitter-rust v0.23.3
Downloaded tree-sitter v0.24.7
Downloaded zlib-rs v0.6.3
Downloaded jiff v0.2.23
Downloaded tree-sitter-c v0.23.4
Downloaded tracing v0.1.44
Downloaded libc v0.2.183
Downloaded unicode-width v0.2.2
Downloaded object v0.37.3
Downloaded tree-sitter-md v0.3.2
Downloaded sha3 v0.10.8
Downloaded libz-sys v1.1.25
Downloaded tracing-subscriber v0.3.23
Downloaded tree-sitter-python v0.23.6
Downloaded curve25519-dalek v4.1.3
Downloaded tree-sitter-go v0.23.4
Downloaded sval v2.17.0
Downloaded tree-sitter-bash v0.23.3
Downloaded yansi v1.0.1
Downloaded typenum v1.19.0
Downloaded tree-sitter-ruby v0.23.1
Downloaded radicle-surf v0.27.1
Downloaded p384 v0.13.1
Downloaded regex-automata v0.4.14
Downloaded tar v0.4.45
Downloaded tokio v1.50.0
Downloaded tree-sitter-typescript v0.23.2
Downloaded unicode-ident v1.0.24
Downloaded schemars v1.2.1
Downloaded rustix v1.1.4
Downloaded syn v2.0.117
Downloaded sha1-checked v0.10.0
Downloaded socket2 v0.5.10
Downloaded similar v2.7.0
Downloaded serde_derive v1.0.228
Downloaded unarray v0.1.4
Downloaded vsimd v0.8.0
Downloaded regex-syntax v0.8.10
Downloaded sqlite3-sys v0.18.0
Downloaded tree-sitter-css v0.23.2
Downloaded sqlite v0.37.0
Downloaded serde_fmt v1.1.0
Downloaded p521 v0.13.3
Downloaded snapbox-macros v0.3.10
Downloaded walkdir v2.5.0
Downloaded universal-hash v0.5.1
Downloaded toml_datetime v0.7.5+spec-1.1.0
Downloaded syn v1.0.109
Downloaded mio v1.1.1
Downloaded gimli v0.32.3
Downloaded clap_builder v4.6.0
Downloaded xattr v1.6.1
Downloaded wait-timeout v0.2.1
Downloaded version_check v0.9.5
Downloaded utf8parse v0.2.2
Downloaded tracing-log v0.2.0
Downloaded synstructure v0.13.2
Downloaded sval_buffer v2.17.0
Downloaded libgit2-sys v0.18.3+1.9.2
Downloaded signals_receipts v0.2.5
Downloaded rsa v0.9.10
Downloaded rand v0.9.2
Downloaded rand v0.8.5
Downloaded unicode-display-width v0.3.0
Downloaded typeid v1.0.3
Downloaded tree-sitter-language v0.1.7
Downloaded tree-sitter-json v0.24.8
Downloaded toml_writer v1.0.7+spec-1.1.0
Downloaded serde_json v1.0.149
Downloaded proptest v1.10.0
Downloaded thiserror-impl v2.0.18
Downloaded thiserror-impl v1.0.69
Downloaded sval_nested v2.17.0
Downloaded subtle v2.6.1
Downloaded structured-logger v1.0.5
Downloaded shell-words v1.1.1
Downloaded ryu v1.0.23
Downloaded regex v1.12.3
Downloaded pem-rfc7468 v0.7.0
Downloaded num-bigint-dig v0.8.6
Downloaded thiserror v1.0.69
Downloaded ssh-key v0.6.7
Downloaded libm v0.2.16
Downloaded idna v1.1.0
Downloaded rand_chacha v0.3.1
Downloaded pretty_assertions v1.4.1
Downloaded git2 v0.20.4
Downloaded sval_dynamic v2.17.0
Downloaded socks5-client v0.4.3
Downloaded itertools v0.14.0
Downloaded prodash v31.0.0
Downloaded simd-adler32 v0.3.8
Downloaded sharded-slab v0.1.7
Downloaded num-bigint v0.4.6
Downloaded flate2 v1.1.9
Downloaded indicatif v0.18.4
Downloaded gix-pack v0.70.0
Downloaded fraction v0.15.3
Downloaded strsim v0.11.1
Downloaded smallvec v1.15.1
Downloaded signature v1.6.4
Downloaded signal-hook-mio v0.2.5
Downloaded shlex v1.3.0
Downloaded jiff-static v0.2.23
Downloaded inquire v0.9.4
Downloaded icu_collections v2.1.1
Downloaded systemd-journal-logger v2.2.2
Downloaded sval_fmt v2.17.0
Downloaded sem_safe v0.2.1
Downloaded jsonschema v0.30.0
Downloaded p256 v0.13.2
Downloaded icu_properties v2.1.2
Downloaded gix-transport v0.57.0
Downloaded serde_derive_internals v0.29.1
Downloaded poly1305 v0.8.0
Downloaded parking_lot v0.12.5
Downloaded getrandom v0.3.4
Downloaded ssh-cipher v0.2.0
Downloaded sha2 v0.10.9
Downloaded semver v1.0.27
Downloaded pkcs8 v0.10.2
Downloaded linux-raw-sys v0.12.1
Downloaded phf v0.11.3
Downloaded cyphernet v0.5.4
Downloaded primeorder v0.13.6
Downloaded polyval v0.6.2
Downloaded pkcs1 v0.7.5
Downloaded icu_normalizer v2.1.1
Downloaded sec1 v0.7.3
Downloaded rand_chacha v0.9.0
Downloaded hmac v0.12.1
Downloaded gix-fs v0.21.1
Downloaded salsa20 v0.10.2
Downloaded pastey v0.2.1
Downloaded icu_provider v2.1.1
Downloaded rusty-fork v0.3.1
Downloaded rustversion v1.0.22
Downloaded icu_normalizer_data v2.1.1
Downloaded proc-macro-error2 v2.0.1
Downloaded litemap v0.8.1
Downloaded gix-odb v0.80.0
Downloaded ref-cast v1.0.25
Downloaded gix-protocol v0.61.0
Downloaded num-integer v0.1.46
Downloaded ed25519-dalek v2.2.0
Downloaded rfc6979 v0.4.0
Downloaded rand_xorshift v0.4.0
Downloaded radicle-git-ext v0.12.0
Downloaded quick-error v1.2.3
Downloaded getrandom v0.2.17
Downloaded proc-macro-error-attr2 v2.0.0
Downloaded ppv-lite86 v0.2.21
Downloaded humantime v2.3.0
Downloaded sqlite3-src v0.7.0
Downloaded human-panic v2.0.6
Downloaded gix-features v0.48.0
Downloaded derive_more-impl v2.1.1
Downloaded chrono v0.4.44
Downloaded miniz_oxide v0.8.9
Downloaded icu_locale_core v2.1.1
Downloaded pbkdf2 v0.12.2
Downloaded pin-project-lite v0.2.17
Downloaded gix-refspec v0.41.0
Downloaded jobserver v0.1.34
Downloaded der v0.7.10
Downloaded cc v1.2.57
Downloaded qcheck-macros v1.0.0
Downloaded potential_utf v0.1.4
Downloaded pkg-config v0.3.32
Downloaded phf_shared v0.11.3
Downloaded parking_lot_core v0.9.12
Downloaded multibase v0.9.2
Downloaded derive_more v2.1.1
Downloaded colored v2.2.0
Downloaded clap v4.6.0
Downloaded arc-swap v1.9.1
Downloaded gix-traverse v0.57.0
Downloaded gix-tempfile v23.0.0
Downloaded nonempty v0.9.0
Downloaded git-ref-format v0.6.0
Downloaded ghash v0.5.1
Downloaded autocfg v1.5.0
Downloaded gix-date v0.15.3
Downloaded bitflags v2.11.0
Downloaded is_terminal_polyfill v1.70.2
Downloaded gix-url v0.36.0
Downloaded gix-packetline v0.21.3
Downloaded git-ref-format-core v0.6.0
Downloaded anyhow v1.0.102
Downloaded fnv v1.0.7
Downloaded base64 v0.21.7
Downloaded num-iter v0.1.45
Downloaded num-cmp v0.1.0
Downloaded gix-trace v0.1.19
Downloaded gix-quote v0.7.1
Downloaded num-complex v0.4.6
Downloaded memmap2 v0.9.10
Downloaded gix-glob v0.26.0
Downloaded gix-actor v0.41.0
Downloaded chacha20 v0.9.1
Downloaded amplify_num v0.5.3
Downloaded byteorder v1.5.0
Downloaded backtrace v0.3.76
Downloaded nonempty v0.12.0
Downloaded noise-framework v0.4.1
Downloaded maybe-async v0.2.10
Downloaded lock_api v0.4.14
Downloaded gix-object v0.60.0
Downloaded data-encoding v2.10.0
Downloaded const-str v0.4.3
Downloaded cipher v0.4.4
Downloaded bit-set v0.8.0
Downloaded anstyle v1.0.14
Downloaded amplify_syn v2.0.1
Downloaded matchers v0.2.0
Downloaded indexmap v2.13.0
Downloaded gix-revision v0.45.0
Downloaded gix-hash v0.25.0
Downloaded generic-array v0.14.7
Downloaded either v1.15.0
Downloaded ctr v0.9.2
Downloaded crypto-bigint v0.5.5
Downloaded crossbeam-utils v0.8.21
Downloaded cbc v0.1.2
Downloaded bytesize v2.3.1
Downloaded bytecount v0.6.9
Downloaded base64ct v1.8.3
Downloaded gix-ref v0.63.0
Downloaded gix-lock v23.0.0
Downloaded block-buffer v0.10.4
Downloaded dyn-clone v1.0.20
Downloaded const-oid v0.9.6
Downloaded bytes v1.11.1
Downloaded ascii v1.1.0
Downloaded hash32 v0.3.1
Downloaded ct-codecs v1.1.6
Downloaded iana-time-zone v0.1.65
Downloaded gix-prompt v0.15.0
Downloaded gix-credentials v0.38.0
Downloaded getrandom v0.4.2
Downloaded base64 v0.22.1
Downloaded anstyle-parse v1.0.0
Downloaded env_filter v1.0.0
Downloaded elliptic-curve v0.13.8
Downloaded data-encoding-macro-internal v0.1.17
Downloaded block-padding v0.3.3
Downloaded gix-validate v0.11.1
Downloaded emojis v0.6.4
Downloaded data-encoding-macro v0.1.19
Downloaded cyphergraphy v0.3.1
Downloaded hashbrown v0.16.1
Downloaded gix-revwalk v0.31.0
Downloaded gix-config-value v0.18.0
Downloaded fluent-uri v0.3.2
Downloaded clap_derive v4.6.0
Downloaded base32 v0.4.0
Downloaded anstream v1.0.0
Downloaded gix-command v0.9.0
Downloaded erased-serde v0.4.10
Downloaded cypheraddr v0.4.1
Downloaded crossterm v0.29.0
Downloaded crossbeam-channel v0.5.15
Downloaded colorchoice v1.0.5
Downloaded base256emoji v1.0.2
Downloaded inout v0.1.4
Downloaded heck v0.5.0
Downloaded gix-sec v0.14.0
Downloaded gix-path v0.12.0
Downloaded gix-negotiate v0.31.0
Downloaded gix-hashtable v0.15.0
Downloaded gix-error v0.2.3
Downloaded gix-commitgraph v0.37.0
Downloaded dunce v1.0.5
Downloaded cfg-if v1.0.4
Downloaded borrow-or-share v0.2.4
Downloaded anstyle-parse v0.2.7
Downloaded convert_case v0.10.0
Downloaded anstream v0.6.21
Downloaded crypto-common v0.1.7
Downloaded cpufeatures v0.2.17
Downloaded chacha20poly1305 v0.10.1
Downloaded base-x v0.2.11
Downloaded document-features v0.2.12
Downloaded ed25519 v1.5.3
Downloaded ecdsa v0.16.9
Downloaded diff v0.1.13
Downloaded crc32fast v1.5.0
Downloaded base16ct v0.2.0
Downloaded displaydoc v0.2.5
Downloaded clap_complete v4.6.0
Downloaded console v0.16.3
Downloaded blowfish v0.9.1
Downloaded bit-vec v0.8.0
Downloaded anstyle-query v1.1.5
Downloaded clap_lex v1.1.0
Downloaded bcrypt-pbkdf v0.10.0
Downloaded ff v0.13.1
Downloaded fast-glob v0.3.3
Downloaded escargot v0.5.15
Downloaded fastrand v2.3.0
Downloaded faster-hex v0.10.0
Downloaded aho-corasick v1.1.4
Downloaded aes v0.8.4
Downloaded aead v0.5.2
Downloaded adler2 v2.0.1
Downloaded addr2line v0.25.1
Compiling libc v0.2.183
Compiling proc-macro2 v1.0.106
Compiling quote v1.0.45
Compiling unicode-ident v1.0.24
Checking cfg-if v1.0.4
Checking zeroize v1.8.2
Compiling version_check v0.9.5
Compiling typenum v1.19.0
Compiling generic-array v0.14.7
Checking getrandom v0.2.17
Checking rand_core v0.6.4
Checking memchr v2.8.0
Compiling syn v2.0.117
Compiling jobserver v0.1.34
Compiling shlex v1.3.0
Compiling find-msvc-tools v0.1.9
Checking crypto-common v0.1.7
Compiling cc v1.2.57
Checking subtle v2.6.1
Compiling serde_core v1.0.228
Checking regex-syntax v0.8.10
Checking aho-corasick v1.1.4
Checking const-oid v0.9.6
Checking smallvec v1.15.1
Checking block-buffer v0.10.4
Checking digest v0.10.7
Checking cpufeatures v0.2.17
Checking stable_deref_trait v1.2.1
Compiling thiserror v2.0.18
Checking fastrand v2.3.0
Compiling parking_lot_core v0.9.12
Checking regex-automata v0.4.14
Checking scopeguard v1.2.0
Checking lock_api v0.4.14
Checking bitflags v2.11.0
Checking parking_lot v0.12.5
Compiling typeid v1.0.3
Checking tinyvec_macros v0.1.1
Compiling crc32fast v1.5.0
Compiling erased-serde v0.4.10
Checking gix-trace v0.1.19
Checking tinyvec v1.11.0
Compiling serde v1.0.228
Checking unicode-normalization v0.1.25
Checking byteorder v1.5.0
Checking itoa v1.0.17
Checking gix-utils v0.3.2
Checking serde_fmt v1.1.0
Compiling thiserror-impl v2.0.18
Compiling serde_derive v1.0.228
Checking hashbrown v0.16.1
Checking bstr v1.12.1
Checking value-bag-serde1 v1.12.0
Compiling synstructure v0.13.2
Checking gix-validate v0.11.1
Checking value-bag v1.12.0
Compiling zerofrom-derive v0.1.6
Checking log v0.4.29
Checking gix-path v0.12.0
Checking same-file v1.0.6
Compiling yoke-derive v0.8.1
Checking walkdir v2.5.0
Checking prodash v31.0.0
Checking zlib-rs v0.6.3
Compiling rustix v1.1.4
Checking zerofrom v0.1.6
Checking yoke v0.8.1
Compiling heapless v0.8.0
Compiling pkg-config v0.3.32
Compiling zerovec-derive v0.11.2
Checking hash32 v0.3.1
Checking gix-features v0.48.0
Compiling libm v0.2.16
Checking linux-raw-sys v0.12.1
Compiling autocfg v1.5.0
Compiling num-traits v0.2.19
Compiling displaydoc v0.2.5
Compiling getrandom v0.4.2
Checking faster-hex v0.10.0
Checking zerovec v0.11.5
Checking block-padding v0.3.3
Compiling zerocopy v0.8.42
Checking inout v0.1.4
Checking sha1 v0.10.6
Checking sha2 v0.10.9
Checking sha1-checked v0.10.0
Checking cipher v0.4.4
Checking tinystr v0.8.2
Checking once_cell v1.21.4
Checking writeable v0.6.2
Checking litemap v0.8.1
Checking percent-encoding v2.3.2
Checking gix-hash v0.25.0
Checking icu_locale_core v2.1.1
Checking potential_utf v0.1.4
Checking zerotrie v0.2.3
Compiling icu_properties_data v2.1.2
Compiling icu_normalizer_data v2.1.1
Compiling zmij v1.0.21
Checking icu_provider v2.1.1
Checking icu_collections v2.1.1
Checking der v0.7.10
Compiling serde_json v1.0.149
Checking equivalent v1.0.2
Checking indexmap v2.13.0
Compiling vcpkg v0.2.15
Compiling syn v1.0.109
Compiling ref-cast v1.0.25
Compiling thiserror v1.0.69
Checking icu_normalizer v2.1.1
Compiling libz-sys v1.1.25
Checking ppv-lite86 v0.2.21
Checking icu_properties v2.1.2
Checking tempfile v3.27.0
Compiling thiserror-impl v1.0.69
Compiling ref-cast-impl v1.0.25
Checking spin v0.9.8
Checking lazy_static v1.5.0
Checking num-integer v0.1.46
Checking idna_adapter v1.2.1
Checking hmac v0.12.1
Checking universal-hash v0.5.1
Checking utf8_iter v1.0.4
Checking opaque-debug v0.3.1
Checking dyn-clone v1.0.20
Compiling tree-sitter-language v0.1.7
Checking idna v1.1.0
Checking spki v0.7.3
Compiling libgit2-sys v0.18.3+1.9.2
Checking signature v2.2.0
Checking ff v0.13.1
Checking base16ct v0.2.0
Checking group v0.13.0
Checking sec1 v0.7.3
Checking rand_chacha v0.3.1
Checking form_urlencoded v1.2.2
Compiling serde_derive_internals v0.29.1
Checking crypto-bigint v0.5.5
Checking elliptic-curve v0.13.8
Compiling schemars_derive v1.2.1
Compiling amplify_syn v2.0.1
Checking rand v0.8.5
Checking url v2.5.8
Checking num-iter v0.1.45
Checking aead v0.5.2
Checking signature v1.6.4
Compiling semver v1.0.27
Checking ed25519 v1.5.3
Checking schemars v1.2.1
Compiling amplify_derive v4.0.1
Compiling rustc_version v0.4.1
Checking poly1305 v0.8.0
Checking rfc6979 v0.4.0
Checking chacha20 v0.9.1
Checking ct-codecs v1.1.6
Checking amplify_num v0.5.3
Checking ascii v1.1.0
Checking ec25519 v0.1.0
Checking ecdsa v0.16.9
Compiling curve25519-dalek v4.1.3
Checking git-ref-format-core v0.6.0
Checking primeorder v0.13.6
Checking amplify v4.9.0
Checking polyval v0.6.2
Checking base64ct v1.8.3
Compiling num-bigint-dig v0.8.6
Checking cyphergraphy v0.3.1
Checking pem-rfc7468 v0.7.0
Checking ghash v0.5.1
Checking pkcs8 v0.10.2
Checking pbkdf2 v0.12.2
Checking aes v0.8.4
Checking ctr v0.9.2
Compiling sqlite3-src v0.7.0
Checking gix-error v0.2.3
Compiling curve25519-dalek-derive v0.1.1
Checking keccak v0.1.6
Checking aes-gcm v0.10.3
Checking sha3 v0.10.8
Checking pkcs1 v0.7.5
Checking ssh-encoding v0.2.0
Checking ed25519 v2.2.3
Checking cbc v0.1.2
Checking blowfish v0.9.1
Checking base32 v0.4.0
Compiling crossbeam-utils v0.8.21
Compiling data-encoding v2.10.0
Checking cypheraddr v0.4.1
Compiling data-encoding-macro-internal v0.1.17
Checking rsa v0.9.10
Checking bcrypt-pbkdf v0.10.0
Checking ssh-cipher v0.2.0
Checking ed25519-dalek v2.2.0
Checking p384 v0.13.1
Checking p521 v0.13.3
Checking p256 v0.13.2
Checking chacha20poly1305 v0.10.1
Checking qcheck v1.0.0
Compiling match-lookup v0.1.2
Checking const-str v0.4.3
Checking data-encoding-macro v0.1.19
Checking noise-framework v0.4.1
Checking base256emoji v1.0.2
Checking ssh-key v0.6.7
Checking socks5-client v0.4.3
Checking secrecy v0.10.3
Checking base-x v0.2.11
Checking multibase v0.9.2
Checking crossbeam-channel v0.5.15
Checking ssh-agent-lib v0.6.0
Checking cyphernet v0.5.4
Checking anstyle-query v1.1.5
Checking errno v0.3.14
Checking utf8parse v0.2.2
Checking jiff v0.2.23
Checking nonempty v0.9.0
Checking siphasher v1.0.2
Checking radicle-localtime v0.1.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-localtime)
Checking radicle-git-metadata v0.2.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-git-metadata)
Checking radicle-dag v0.10.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-dag)
Checking colorchoice v1.0.5
Checking is_terminal_polyfill v1.70.2
Checking anstyle v1.0.14
Checking radicle-git-ref-format v0.1.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-git-ref-format)
Checking gix-hashtable v0.15.0
Checking base64 v0.21.7
Compiling unicode-segmentation v1.12.0
Compiling radicle v0.24.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle)
Compiling signal-hook v0.3.18
Compiling convert_case v0.10.0
Checking gix-date v0.15.3
Checking signal-hook-registry v1.4.8
Checking gix-actor v0.41.0
Checking gix-object v0.60.0
Checking serde-untagged v0.1.9
Checking bytesize v2.3.1
Checking memmap2 v0.9.10
Checking dunce v1.0.5
Checking nonempty v0.12.0
Checking fast-glob v0.3.3
Compiling derive_more-impl v2.1.1
Checking gix-chunk v0.7.1
Checking mio v1.1.1
Checking regex v1.12.3
Checking sem_safe v0.2.1
Compiling litrs v1.0.0
Compiling portable-atomic v1.13.1
Checking unicode-width v0.2.2
Compiling document-features v0.2.12
Checking signals_receipts v0.2.5
Checking derive_more v2.1.1
Checking signal-hook-mio v0.2.5
Checking gix-commitgraph v0.37.0
Checking anstyle-parse v0.2.7
Checking anstream v0.6.21
Checking gix-revwalk v0.31.0
Checking crossterm v0.29.0
Checking console v0.16.3
Checking gix-fs v0.21.1
Checking unit-prefix v0.5.2
Checking gix-tempfile v23.0.0
Checking indicatif v0.18.4
Checking inquire v0.9.4
Checking unicode-display-width v0.3.0
Checking radicle-signals v0.11.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-signals)
Checking gix-quote v0.7.1
Checking shell-words v1.1.1
Checking iana-time-zone v0.1.65
Checking either v1.15.0
Checking gix-command v0.9.0
Checking chrono v0.4.44
Checking colored v2.2.0
Compiling rustversion v1.0.22
Compiling object v0.37.3
Checking gix-lock v23.0.0
Checking gix-url v0.36.0
Checking gix-config-value v0.18.0
Checking gix-sec v0.14.0
Checking gimli v0.32.3
Checking adler2 v2.0.1
Checking miniz_oxide v0.8.9
Checking gix-prompt v0.15.0
Checking addr2line v0.25.1
Checking gix-traverse v0.57.0
Checking gix-revision v0.45.0
Checking gix-diff v0.63.0
Checking gix-packetline v0.21.3
Checking gix-glob v0.26.0
Compiling tree-sitter v0.24.7
Compiling anyhow v1.0.102
Checking rustc-demangle v0.1.27
Checking backtrace v0.3.76
Checking gix-refspec v0.41.0
Checking gix-transport v0.57.0
Checking gix-pack v0.70.0
Checking arc-swap v1.9.1
Checking gix-credentials v0.38.0
Checking gix-shallow v0.12.0
Checking gix-ref v0.63.0
Checking gix-negotiate v0.31.0
Compiling maybe-async v0.2.10
Compiling proc-macro-error-attr2 v2.0.0
Compiling getrandom v0.3.4
Compiling simd-adler32 v0.3.8
Checking gix-protocol v0.61.0
Compiling proc-macro-error2 v2.0.1
Checking gix-odb v0.80.0
Compiling xattr v1.6.1
Compiling filetime v0.2.27
Checking anstyle-parse v1.0.0
Checking uuid v1.22.0
Checking bytes v1.11.1
Checking anstream v1.0.0
Compiling tar v0.4.45
Compiling flate2 v1.1.9
Compiling git-ref-format-macro v0.6.0
Checking snapbox-macros v0.3.10
Checking salsa20 v0.10.2
Checking normalize-line-endings v0.3.0
Compiling heck v0.5.0
Checking similar v2.7.0
Checking siphasher v0.3.11
Checking clap_lex v1.1.0
Checking strsim v0.11.1
Checking streaming-iterator v0.1.9
Checking snapbox v0.4.17
Checking clap_builder v4.6.0
Checking bloomy v1.2.0
Compiling clap_derive v4.6.0
Compiling radicle-surf v0.27.1
Checking sqlite3-sys v0.18.0
Checking sqlite v0.37.0
Checking scrypt v0.11.0
Checking git-ref-format v0.6.0
Checking systemd-journal-logger v2.2.2
Checking radicle-crypto v0.17.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-crypto)
Checking serde_spanned v1.0.4
Checking toml_datetime v0.7.5+spec-1.1.0
Compiling tree-sitter-json v0.24.8
Compiling tree-sitter-python v0.23.6
Compiling tree-sitter-md v0.3.2
Compiling tree-sitter-toml-ng v0.6.0
Compiling tree-sitter-bash v0.23.3
Compiling tree-sitter-html v0.23.2
Compiling tree-sitter-rust v0.23.3
Compiling tree-sitter-typescript v0.23.2
Compiling tree-sitter-css v0.23.2
Compiling tree-sitter-c v0.23.4
Compiling tree-sitter-ruby v0.23.1
Compiling tree-sitter-go v0.23.4
Checking pin-project-lite v0.2.17
Checking toml_writer v1.0.7+spec-1.1.0
Checking radicle-std-ext v0.2.0
Checking toml v0.9.12+spec-1.1.0
Checking tokio v1.50.0
Checking clap v4.6.0
Checking sysinfo v0.37.2
Compiling radicle-node v0.20.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-node)
Checking yansi v1.0.1
Checking diff v0.1.13
Compiling radicle-cli v0.21.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-cli)
Checking pretty_assertions v1.4.1
Checking human-panic v2.0.6
Checking clap_complete v4.6.0
Checking structured-logger v1.0.5
Checking radicle-systemd v0.13.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-systemd)
Checking tree-sitter-highlight v0.24.7
Checking itertools v0.14.0
Compiling qcheck-macros v1.0.0
Checking socket2 v0.5.10
Checking humantime v2.3.0
Checking lexopt v0.3.2
Checking timeago v0.4.2
Compiling escargot v0.5.15
Checking bit-vec v0.8.0
Checking bit-set v0.8.0
Checking rand_core v0.9.5
Checking num-bigint v0.4.6
Compiling ahash v0.8.12
Checking num-complex v0.4.6
Checking env_filter v1.0.0
Checking borrow-or-share v0.2.4
Checking fluent-uri v0.3.2
Checking env_logger v0.11.9
Checking phf_shared v0.11.3
Checking num-rational v0.4.2
Compiling test-log-macros v0.2.19
Checking wait-timeout v0.2.1
Checking fnv v1.0.7
Checking num v0.4.3
Checking quick-error v1.2.3
Checking vsimd v0.8.0
Compiling radicle-remote-helper v0.17.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-remote-helper)
Checking outref v0.5.2
Checking rusty-fork v0.3.1
Checking fraction v0.15.3
Checking uuid-simd v0.8.0
Checking test-log v0.2.19
Checking phf v0.11.3
Checking referencing v0.30.0
Checking rand_chacha v0.9.0
Checking rand_xorshift v0.4.0
Checking rand v0.9.2
Checking fancy-regex v0.14.0
Checking email_address v0.2.9
Checking unarray v0.1.4
Checking bytecount v0.6.9
Checking base64 v0.22.1
Checking num-cmp v0.1.0
Checking proptest v1.10.0
Checking emojis v0.6.4
Checking jsonschema v0.30.0
Compiling pastey v0.2.1
Checking radicle-windows v0.1.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-windows)
Checking git2 v0.20.4
Checking radicle-oid v0.2.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-oid)
Checking radicle-term v0.18.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-term)
Checking radicle-git-ext v0.12.0
Checking radicle-core v0.3.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-core)
Checking radicle-cob v0.20.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-cob)
Checking radicle-log v0.1.0 (/50865425-b8ce-4e19-bc7c-947c733e23ad/w/crates/radicle-log)
error[E0433]: cannot find module or crate `read` in this scope
--> crates/radicle/src/git/repository/adapter/git2/object.rs:69:67
|
69 | fn object_kind(&self, oid: Oid) -> Result<Option<ObjectKind>, read::ObjectKind> {
| ^^^^ use of unresolved module or unlinked crate `read`
|
= help: if you wanted to use a crate named `read`, use `cargo add read` to add it to your `Cargo.toml`
note: module `crate::storage::git::trailers::object::error::read` exists but is inaccessible
--> crates/radicle/src/git/repository/object/error.rs:3:1
|
3 | pub mod read;
| ^^^^^^^^^^^^^ not accessible
error[E0433]: cannot find module or crate `read` in this scope
--> crates/radicle/src/git/repository/adapter/git2/object.rs:70:38
|
70 | let odb = self.odb().map_err(read::ObjectKind::backend)?;
| ^^^^ use of unresolved module or unlinked crate `read`
|
= help: if you wanted to use a crate named `read`, use `cargo add read` to add it to your `Cargo.toml`
note: these enums exist but are inaccessible
--> crates/radicle/src/git/repository/object/error/read.rs:130:1
|
130 | pub enum ObjectKind {
| ^^^^^^^^^^^^^^^^^^^ `crate::storage::git::trailers::object::error::read::ObjectKind`: not accessible
|
::: crates/radicle/src/git/repository/types.rs:19:1
|
19 | pub enum ObjectKind {
| ^^^^^^^^^^^^^^^^^^^ `crate::storage::git::trailers::repository::ObjectKind`: not accessible
error[E0433]: cannot find module or crate `read` in this scope
--> crates/radicle/src/git/repository/adapter/git2/object.rs:74:27
|
74 | Err(e) => Err(read::ObjectKind::backend(e)),
| ^^^^ use of unresolved module or unlinked crate `read`
|
= help: if you wanted to use a crate named `read`, use `cargo add read` to add it to your `Cargo.toml`
note: these enums exist but are inaccessible
--> crates/radicle/src/git/repository/object/error/read.rs:130:1
|
130 | pub enum ObjectKind {
| ^^^^^^^^^^^^^^^^^^^ `crate::storage::git::trailers::object::error::read::ObjectKind`: not accessible
|
::: crates/radicle/src/git/repository/types.rs:19:1
|
19 | pub enum ObjectKind {
| ^^^^^^^^^^^^^^^^^^^ `crate::storage::git::trailers::repository::ObjectKind`: not accessible
error[E0433]: cannot find module or crate `read` in this scope
--> crates/radicle/src/git/repository/adapter/git2/object.rs:69:67
|
69 | fn object_kind(&self, oid: Oid) -> Result<Option<ObjectKind>, read::ObjectKind> {
| ^^^^ use of unresolved module or unlinked crate `read`
|
= help: if you wanted to use a crate named `read`, use `cargo add read` to add it to your `Cargo.toml`
note: module `crate::storage::git::tests::object::error::read` exists but is inaccessible
--> crates/radicle/src/git/repository/object/error.rs:3:1
|
3 | pub mod read;
| ^^^^^^^^^^^^^ not accessible
error[E0433]: cannot find module or crate `read` in this scope
--> crates/radicle/src/git/repository/adapter/git2/object.rs:70:38
|
70 | let odb = self.odb().map_err(read::ObjectKind::backend)?;
| ^^^^ use of unresolved module or unlinked crate `read`
|
= help: if you wanted to use a crate named `read`, use `cargo add read` to add it to your `Cargo.toml`
note: these enums exist but are inaccessible
--> crates/radicle/src/git/repository/object/error/read.rs:130:1
|
130 | pub enum ObjectKind {
| ^^^^^^^^^^^^^^^^^^^ `crate::storage::git::tests::object::error::read::ObjectKind`: not accessible
|
::: crates/radicle/src/git/repository/types.rs:19:1
|
19 | pub enum ObjectKind {
| ^^^^^^^^^^^^^^^^^^^ `crate::storage::git::tests::repository::ObjectKind`: not accessible
error[E0433]: cannot find module or crate `read` in this scope
--> crates/radicle/src/git/repository/adapter/git2/object.rs:74:27
|
74 | Err(e) => Err(read::ObjectKind::backend(e)),
| ^^^^ use of unresolved module or unlinked crate `read`
|
= help: if you wanted to use a crate named `read`, use `cargo add read` to add it to your `Cargo.toml`
note: these enums exist but are inaccessible
--> crates/radicle/src/git/repository/object/error/read.rs:130:1
|
130 | pub enum ObjectKind {
| ^^^^^^^^^^^^^^^^^^^ `crate::storage::git::tests::object::error::read::ObjectKind`: not accessible
|
::: crates/radicle/src/git/repository/types.rs:19:1
|
19 | pub enum ObjectKind {
| ^^^^^^^^^^^^^^^^^^^ `crate::storage::git::tests::repository::ObjectKind`: not accessible
For more information about this error, try `rustc --explain E0433`.
error: could not compile `radicle` (lib) due to 3 previous errors
warning: build failed, waiting for other jobs to finish...
error: could not compile `radicle` (lib test) due to 3 previous errors
Exit code: 101
{
"response": "finished",
"result": "failure"
}