diff --git a/crates/radicle-fetch/src/stage.rs b/crates/radicle-fetch/src/stage.rs index d5ff1753..6de59c63 100644 --- a/crates/radicle-fetch/src/stage.rs +++ b/crates/radicle-fetch/src/stage.rs @@ -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//refs/rad/id"`. + NamespacedRadId { namespace: PublicKey }, + /// Represents `"refs/namespaces//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>; + fn ls_refs(&self) -> Option>; /// Filter a remote-advertised [`Ref`]. /// @@ -163,8 +199,8 @@ pub struct CanonicalId { } impl ProtocolStage for CanonicalId { - fn ls_refs(&self) -> Option> { - Some(NonEmpty::new(refs::REFS_RAD_ID.as_bstr().into())) + fn ls_refs(&self) -> Option> { + Some(NonEmpty::new(RefPrefix::RadId)) } fn ref_filter(&self, r: Ref) -> Option { @@ -250,17 +286,17 @@ pub struct SpecialRefs { } impl ProtocolStage for SpecialRefs { - fn ls_refs(&self) -> Option> { + fn ls_refs(&self) -> Option> { 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> { + fn ls_refs(&self) -> Option> { // 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> { + fn ls_refs(&self) -> Option> { None } diff --git a/crates/radicle-fetch/src/state.rs b/crates/radicle-fetch/src/state.rs index 42469ab5..9c71bdc7 100644 --- a/crates/radicle-fetch/src/state.rs +++ b/crates/radicle-fetch/src/state.rs @@ -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::>(), diff --git a/crates/radicle-fetch/src/transport.rs b/crates/radicle-fetch/src/transport.rs index 1e5d13c2..22ae0242 100644 --- a/crates/radicle-fetch/src/transport.rs +++ b/crates/radicle-fetch/src/transport.rs @@ -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, + prefixes: impl IntoIterator, handshake: &handshake::Outcome, ) -> Result, Error> { - prefixes.sort(); - prefixes.dedup(); + let prefixes = prefixes.into_iter().collect::>(); let (read, write) = self.stream.open(); Ok(ls_refs::run( ls_refs::Config { diff --git a/crates/radicle-fetch/src/transport/ls_refs.rs b/crates/radicle-fetch/src/transport/ls_refs.rs index 16c33fc9..4a9168f6 100644 --- a/crates/radicle-fetch/src/transport/ls_refs.rs +++ b/crates/radicle-fetch/src/transport/ls_refs.rs @@ -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, + pub prefixes: BTreeSet, } /// 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::>(); + 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();