fetch: introduce domain type RefPrefix

Better represent the domain by introducing the `RefPrefix` type.
It captures the different `ref-prefix` values that can be used in the
`ls-refs` step of the protocol.

These are turned into their equivalent `ref-prefix` values using
`RefPrefix::into_bstring`.

This better connects the `ProtocolStage::ls_refs` method with the
`ls_refs::run` through the use of the `RefPrefix` type, rather than
any set of `BString` values.
This commit is contained in:
Fintan Halpenny 2026-02-16 13:37:13 +00:00
parent 8070f98a5b
commit 391571178e
4 changed files with 68 additions and 22 deletions

View File

@ -89,6 +89,42 @@ pub mod error {
}
}
/// A `ref-prefix` used in the `ls-refs` step of the fetch protocol.
///
/// Since the Radicle protocol only wants to filter by very specific references,
/// this type captures the possible reference prefixes.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum RefPrefix {
/// Represents `refs/rad/id`.
RadId,
/// Represents `"refs/namespaces/<namespace>/refs/rad/id"`.
NamespacedRadId { namespace: PublicKey },
/// Represents `"refs/namespaces/<namespace>/refs/rad/sigrefs"`.
NamespacedRadSigrefs { namespace: PublicKey },
/// Represents `"refs/namespaces"`
AllNamespaces,
}
impl RefPrefix {
/// Convert the [`RefPrefix`] into its equivalent [`BString`].
///
/// See the [`RefPrefix`] variants for their [`BString`] values.
pub fn into_bstring(self) -> BString {
match self {
RefPrefix::RadId => refs::REFS_RAD_ID.as_bstr().into(),
RefPrefix::NamespacedRadId { namespace } => {
radicle::git::refs::storage::id(&namespace).as_bstr().into()
}
RefPrefix::NamespacedRadSigrefs { namespace } => {
radicle::git::refs::storage::sigrefs(&namespace)
.as_bstr()
.into()
}
RefPrefix::AllNamespaces => "refs/namespaces".into(),
}
}
}
/// A [`ProtocolStage`] describes a single roundtrip with the Radicle
/// node that is serving the data.
///
@ -107,7 +143,7 @@ pub mod error {
/// refdb (in-memory and production).
pub(crate) trait ProtocolStage {
/// If and how to perform `ls-refs`.
fn ls_refs(&self) -> Option<NonEmpty<BString>>;
fn ls_refs(&self) -> Option<NonEmpty<RefPrefix>>;
/// Filter a remote-advertised [`Ref`].
///
@ -163,8 +199,8 @@ pub struct CanonicalId {
}
impl ProtocolStage for CanonicalId {
fn ls_refs(&self) -> Option<NonEmpty<BString>> {
Some(NonEmpty::new(refs::REFS_RAD_ID.as_bstr().into()))
fn ls_refs(&self) -> Option<NonEmpty<RefPrefix>> {
Some(NonEmpty::new(RefPrefix::RadId))
}
fn ref_filter(&self, r: Ref) -> Option<ReceivedRef> {
@ -250,17 +286,17 @@ pub struct SpecialRefs {
}
impl ProtocolStage for SpecialRefs {
fn ls_refs(&self) -> Option<NonEmpty<BString>> {
fn ls_refs(&self) -> Option<NonEmpty<RefPrefix>> {
match &self.followed {
policy::Allowed::All => Some(NonEmpty::new("refs/namespaces".into())),
policy::Allowed::All => Some(NonEmpty::new(RefPrefix::AllNamespaces)),
policy::Allowed::Followed { remotes } => NonEmpty::collect(
remotes
.iter()
.chain(self.delegates.iter())
.flat_map(|remote| {
[
BString::from(radicle::git::refs::storage::id(remote).to_string()),
BString::from(radicle::git::refs::storage::sigrefs(remote).to_string()),
RefPrefix::NamespacedRadSigrefs { namespace: *remote },
RefPrefix::NamespacedRadId { namespace: *remote },
]
}),
),
@ -331,12 +367,16 @@ pub struct SigrefsAt {
}
impl ProtocolStage for SigrefsAt {
fn ls_refs(&self) -> Option<NonEmpty<BString>> {
fn ls_refs(&self) -> Option<NonEmpty<RefPrefix>> {
// N.b. the `Oid`s are known but the `rad/sigrefs` are still
// asked for to mark them for updating the fetch state.
NonEmpty::collect(self.refs_at.iter().map(|refs_at| {
BString::from(radicle::git::refs::storage::sigrefs(&refs_at.remote).to_string())
}))
NonEmpty::collect(
self.refs_at
.iter()
.map(|refs_at| RefPrefix::NamespacedRadSigrefs {
namespace: refs_at.remote,
}),
)
}
// We only asked for `rad/sigrefs` so we should only get
@ -421,7 +461,7 @@ pub struct DataRefs {
impl ProtocolStage for DataRefs {
// We don't need to ask for refs since we have all reference names
// and `Oid`s in `rad/sigrefs`.
fn ls_refs(&self) -> Option<NonEmpty<BString>> {
fn ls_refs(&self) -> Option<NonEmpty<RefPrefix>> {
None
}

View File

@ -225,7 +225,7 @@ impl FetchState {
let refs = match step.ls_refs() {
Some(refs) => handle
.transport
.ls_refs(refs.into(), handshake)?
.ls_refs(refs, handshake)?
.into_iter()
.filter_map(|r| step.ref_filter(r))
.collect::<Vec<_>>(),

View File

@ -20,6 +20,7 @@ use thiserror::Error;
use crate::git::packfile::Keepfile;
use crate::git::repository;
use crate::stage::RefPrefix;
/// Open a reader and writer stream to pass to the ls-refs and fetch
/// processes for communicating during their respective protocols.
@ -104,11 +105,10 @@ where
/// Perform ls-refs with the server side.
pub(crate) fn ls_refs(
&mut self,
mut prefixes: Vec<BString>,
prefixes: impl IntoIterator<Item = RefPrefix>,
handshake: &handshake::Outcome,
) -> Result<Vec<handshake::Ref>, Error> {
prefixes.sort();
prefixes.dedup();
let prefixes = prefixes.into_iter().collect::<BTreeSet<_>>();
let (read, write) = self.stream.open();
Ok(ls_refs::run(
ls_refs::Config {

View File

@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::io;
use gix_features::progress::Progress;
@ -7,6 +8,8 @@ use gix_protocol::ls_refs;
use gix_protocol::transport::Protocol;
use gix_transport::bstr::{BString, ByteVec};
use crate::stage::RefPrefix;
use super::{agent_name, Connection};
/// Configuration for running an ls-refs process.
@ -17,7 +20,7 @@ pub struct Config {
#[allow(dead_code)]
pub repo: BString,
/// Ref prefixes for filtering the output of the ls-refs process.
pub prefixes: Vec<BString>,
pub prefixes: BTreeSet<RefPrefix>,
}
/// Run the ls-refs process using the provided `config`.
@ -51,11 +54,17 @@ where
)));
}
let prefixes = config
.prefixes
.into_iter()
.map(|prefix| prefix.into_bstring())
.collect::<BTreeSet<_>>();
let refs = gix_protocol::ls_refs(
&mut conn,
capabilities,
|_caps, args, features| {
for prefix in &config.prefixes {
for prefix in &prefixes {
let mut arg = BString::from("ref-prefix ");
arg.push_str(prefix);
args.push(arg)
@ -74,10 +83,7 @@ where
.into_iter()
.filter(|r| {
let (refname, _, _) = r.unpack();
config
.prefixes
.iter()
.any(|prefix| refname.starts_with(prefix))
prefixes.iter().any(|prefix| refname.starts_with(prefix))
})
.collect();