305 lines · 10.7 KB · Rust
RawDownload
#[cfg(any(feature = "blocking-client", feature = "async-client"))]mod error {    use crate::handshake::refs::parse;     /// The error returned by invoking a [`super::function::LsRefsCommand`].    #[derive(Debug, thiserror::Error)]    #[expect(missing_docs)]    pub enum Error {        #[error(transparent)]        Io(#[from] std::io::Error),        #[error(transparent)]        Transport(#[from] gix_transport::client::Error),        #[error(transparent)]        Parse(#[from] parse::Error),        #[error(transparent)]        ArgumentValidation(#[from] crate::command::validate_argument_prefixes::Error),    }     impl gix_transport::IsSpuriousError for Error {        fn is_spurious(&self) -> bool {            match self {                Error::Io(err) => err.is_spurious(),                Error::Transport(err) => err.is_spurious(),                _ => false,            }        }    }}#[cfg(any(feature = "blocking-client", feature = "async-client"))]pub use error::Error; #[cfg(any(feature = "blocking-client", feature = "async-client"))]pub use self::function::RefPrefixes; #[cfg(any(feature = "blocking-client", feature = "async-client"))]pub(crate) mod function {    use std::collections::HashSet;     use bstr::{BString, ByteVec};    use gix_features::progress::Progress;    use gix_transport::client::Capabilities;     use super::Error;    #[cfg(feature = "async-client")]    use crate::transport::client::async_io::TransportV2Ext as _;    #[cfg(feature = "blocking-client")]    use crate::transport::client::blocking_io::TransportV2Ext as _;    use crate::{Command, handshake::Ref};     /// [`RefPrefixes`] are the set of prefixes that are sent to the server for    /// filtering purposes.    ///    /// These are communicated by sending zero or more `ref-prefix` values, and    /// are documented in [gitprotocol-v2.adoc#ls-refs].    ///    /// These prefixes can be constructed from a set of [`RefSpec`]'s using    /// [`RefPrefixes::from_refspecs`].    ///    /// Alternatively, they can be constructed using [`RefPrefixes::new`] and    /// using [`RefPrefixes::extend`] to add new prefixes.    ///    /// [`RefSpec`]: gix_refspec::RefSpec    /// [gitprotocol-v2.adoc#ls-refs]: https://github.com/git/git/blob/master/Documentation/gitprotocol-v2.adoc#ls-refs    pub struct RefPrefixes {        prefixes: Vec<BString>,    }     impl Default for RefPrefixes {        fn default() -> Self {            Self::new()        }    }     impl RefPrefixes {        /// Create an empty set of [`RefPrefixes`].        pub fn new() -> RefPrefixes {            RefPrefixes { prefixes: Vec::new() }        }         /// Convert a series of [`RefSpec`]'s into a set of [`RefPrefixes`].        ///        /// It attempts to expand each [`RefSpec`] into prefix references, e.g.        /// `refs/heads/`, `refs/remotes/`, `refs/namespaces/foo/`, etc.        ///        /// Inputs that aren't fully qualified refs, like `HEAD` or `main`, are        /// expanded in the same DWIM-style way that Git uses for `ref-prefix`        /// generation, yielding prefixes like `HEAD`, `refs/heads/main`, and        /// other rev-parse candidates.        ///        /// [`RefSpec`]: gix_refspec::RefSpec        pub fn from_refspecs<'a>(refspecs: impl IntoIterator<Item = &'a gix_refspec::RefSpec>) -> Self {            let mut seen = HashSet::new();            let mut prefixes = Self::new();            for spec in refspecs.into_iter() {                let spec = spec.to_ref();                if seen.insert(spec.instruction()) {                    let mut out = Vec::with_capacity(1);                    spec.expand_prefixes(&mut out);                    prefixes.extend(out);                }            }            prefixes        }         fn into_args(self) -> impl Iterator<Item = BString> {            self.prefixes.into_iter().map(|mut prefix| {                prefix.insert_str(0, "ref-prefix ");                prefix            })        }    }     impl Extend<BString> for RefPrefixes {        fn extend<T: IntoIterator<Item = BString>>(&mut self, iter: T) {            for prefix in iter {                if !self.prefixes.iter().any(|existing| existing == &prefix) {                    self.prefixes.push(prefix);                }            }        }    }     /// A command to list references from a remote Git repository.    ///    /// Its invocation uses the same implementation with either blocking or asynchronous I/O.    pub struct LsRefsCommand<'a> {        pub(crate) capabilities: &'a Capabilities,        features: Vec<crate::command::Feature>,        arguments: Vec<BString>,    }     macro_rules! invoke {        ($name:ident, $bisync:path, $transport:path, $from_v2_refs:path, $mode:literal) => {            /// Invoke a ls-refs V2 command on `transport`.            ///            /// `progress` is used to provide feedback.            /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate.            #[$bisync]            pub async fn $name(                self,                mut transport: impl $transport,                progress: &mut impl Progress,                trace: bool,            ) -> Result<Vec<Ref>, Error> {                let _span = gix_features::trace::detail!("gix_protocol::LsRefsCommand::invoke()", mode = $mode);                Command::LsRefs.validate_argument_prefixes(                    gix_transport::Protocol::V2,                    self.capabilities,                    &self.arguments,                    &self.features,                )?;                 progress.step();                progress.set_name("list refs".into());                let mut remote_refs = transport                    .invoke(                        Command::LsRefs.as_str(),                        self.features.into_iter(),                        if self.arguments.is_empty() {                            None                        } else {                            Some(self.arguments.into_iter())                        },                        trace,                    )                    .await?;                Ok($from_v2_refs(&mut remote_refs).await?)            }        };    }     impl<'a> LsRefsCommand<'a> {        /// Build a command to list refs from the given server `capabilities`,        /// using `agent` information to identify ourselves.        ///        /// Use [`crate::ls_refs::RefPrefixes::from_refspecs()`] to construct `ref_prefixes`        /// from refspecs, or [`crate::ls_refs::RefPrefixes::new()`] to build them manually.        pub fn new(            ref_prefixes: Option<RefPrefixes>,            capabilities: &'a Capabilities,            agent: crate::command::Feature,        ) -> Self {            let ls_refs = Command::LsRefs;            let mut features = ls_refs.default_features(gix_transport::Protocol::V2, capabilities);            features.push(agent);            let mut arguments = ls_refs.initial_v2_arguments(&features);            if capabilities                .capability("ls-refs")                .and_then(|cap| cap.supports("unborn"))                .unwrap_or_default()            {                arguments.push("unborn".into());            }             if let Some(prefixes) = ref_prefixes {                arguments.extend(prefixes.into_args());            }             Self {                capabilities,                features,                arguments,            }        }         #[cfg(feature = "async-client")]        invoke!(            invoke_async,            ::bisync::asynchronous::bisync,            crate::transport::client::async_io::Transport,            crate::handshake::refs::async_io::from_v2_refs,            "async"        );         #[cfg(feature = "blocking-client")]        invoke!(            invoke_blocking,            ::bisync::synchronous::bisync,            crate::transport::client::blocking_io::Transport,            crate::handshake::refs::blocking_io::from_v2_refs,            "blocking"        );    }     #[cfg(test)]    mod ref_prefixes {        use bstr::{BString, ByteSlice};         use super::RefPrefixes;         #[test]        fn extend_preserves_first_seen_order_and_deduplicates_prefixes() {            let mut prefixes = RefPrefixes::new();            prefixes.extend(                [                    "refs/tags",                    "HEAD",                    "main",                    "refs/heads/main",                    "refs/tags",                    "HEAD",                    "refs/heads/feature",                    "refs/heads/main",                ]                .into_iter()                .map(|prefix| prefix.as_bytes().as_bstr().to_owned()),            );             assert_eq!(                prefixes.into_args().collect::<Vec<_>>(),                [                    "ref-prefix refs/tags",                    "ref-prefix HEAD",                    "ref-prefix main",                    "ref-prefix refs/heads/main",                    "ref-prefix refs/heads/feature"                ]                .into_iter()                .map(BString::from)                .collect::<Vec<_>>()            );        }         #[test]        fn from_refspecs_keeps_exact_refs_and_dwim_expansions() {            let specs = [                gix_refspec::parse("HEAD".into(), gix_refspec::parse::Operation::Fetch)                    .expect("valid")                    .to_owned(),                gix_refspec::parse("dwim".into(), gix_refspec::parse::Operation::Fetch)                    .expect("valid")                    .to_owned(),                gix_refspec::parse(                    "refs/tags/prefix*:refs/tags/prefix*".into(),                    gix_refspec::parse::Operation::Fetch,                )                .expect("valid")                .to_owned(),                gix_refspec::parse("refs/heads/main".into(), gix_refspec::parse::Operation::Fetch)                    .expect("valid")                    .to_owned(),            ];             let prefixes = RefPrefixes::from_refspecs(&specs);             assert_eq!(                prefixes.into_args().collect::<Vec<_>>(),                [                    "ref-prefix HEAD",                    "ref-prefix dwim",                    "ref-prefix refs/dwim",                    "ref-prefix refs/tags/dwim",                    "ref-prefix refs/heads/dwim",                    "ref-prefix refs/remotes/dwim",                    "ref-prefix refs/remotes/dwim/HEAD",                    "ref-prefix refs/tags/prefix",                    "ref-prefix refs/heads/main",                ]                .into_iter()                .map(BString::from)                .collect::<Vec<_>>()            );        }    }}