gix_protocol/
ls_refs.rs

1#[cfg(any(feature = "blocking-client", feature = "async-client"))]
2mod error {
3    use crate::handshake::refs::parse;
4
5    /// The error returned by invoking a [`super::function::LsRefsCommand`].
6    #[derive(Debug, thiserror::Error)]
7    #[allow(missing_docs)]
8    pub enum Error {
9        #[error(transparent)]
10        Io(#[from] std::io::Error),
11        #[error(transparent)]
12        Transport(#[from] gix_transport::client::Error),
13        #[error(transparent)]
14        Parse(#[from] parse::Error),
15        #[error(transparent)]
16        ArgumentValidation(#[from] crate::command::validate_argument_prefixes::Error),
17    }
18
19    impl gix_transport::IsSpuriousError for Error {
20        fn is_spurious(&self) -> bool {
21            match self {
22                Error::Io(err) => err.is_spurious(),
23                Error::Transport(err) => err.is_spurious(),
24                _ => false,
25            }
26        }
27    }
28}
29#[cfg(any(feature = "blocking-client", feature = "async-client"))]
30pub use error::Error;
31
32#[cfg(any(feature = "blocking-client", feature = "async-client"))]
33pub(crate) mod function {
34    use std::{borrow::Cow, collections::HashSet};
35
36    use bstr::{BString, ByteVec};
37    use gix_features::progress::Progress;
38    use gix_transport::client::Capabilities;
39
40    use super::Error;
41    #[cfg(feature = "async-client")]
42    use crate::transport::client::async_io::{self, TransportV2Ext as _};
43    #[cfg(feature = "blocking-client")]
44    use crate::transport::client::blocking_io::{self, TransportV2Ext as _};
45    use crate::{
46        handshake::{refs::from_v2_refs, Ref},
47        Command,
48    };
49
50    /// A command to list references from a remote Git repository.
51    ///
52    /// It acts as a utility to separate the invocation into the shared blocking portion,
53    /// and the one that performs IO either blocking or `async`.
54    pub struct LsRefsCommand<'a> {
55        pub(crate) capabilities: &'a Capabilities,
56        features: Vec<(&'static str, Option<Cow<'static, str>>)>,
57        arguments: Vec<BString>,
58    }
59
60    impl<'a> LsRefsCommand<'a> {
61        /// Build a command to list refs from the given server `capabilities`,
62        /// using `agent` information to identify ourselves.
63        pub fn new(
64            prefix_refspecs: Option<&[gix_refspec::RefSpec]>,
65            capabilities: &'a Capabilities,
66            agent: (&'static str, Option<Cow<'static, str>>),
67        ) -> Self {
68            let ls_refs = Command::LsRefs;
69            let mut features = ls_refs.default_features(gix_transport::Protocol::V2, capabilities);
70            features.push(agent);
71            let mut arguments = ls_refs.initial_v2_arguments(&features);
72            if capabilities
73                .capability("ls-refs")
74                .and_then(|cap| cap.supports("unborn"))
75                .unwrap_or_default()
76            {
77                arguments.push("unborn".into());
78            }
79
80            if let Some(refspecs) = prefix_refspecs {
81                let mut seen = HashSet::new();
82                for spec in refspecs {
83                    let spec = spec.to_ref();
84                    if seen.insert(spec.instruction()) {
85                        let mut prefixes = Vec::with_capacity(1);
86                        spec.expand_prefixes(&mut prefixes);
87                        for mut prefix in prefixes {
88                            prefix.insert_str(0, "ref-prefix ");
89                            arguments.push(prefix);
90                        }
91                    }
92                }
93            }
94
95            Self {
96                capabilities,
97                features,
98                arguments,
99            }
100        }
101
102        /// Invoke a ls-refs V2 command on `transport`.
103        ///
104        /// `progress` is used to provide feedback.
105        /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate.
106        #[cfg(feature = "async-client")]
107        pub async fn invoke_async(
108            self,
109            mut transport: impl async_io::Transport,
110            progress: &mut impl Progress,
111            trace: bool,
112        ) -> Result<Vec<Ref>, Error> {
113            let _span = gix_features::trace::detail!("gix_protocol::LsRefsCommand::invoke_async()");
114            Command::LsRefs.validate_argument_prefixes(
115                gix_transport::Protocol::V2,
116                self.capabilities,
117                &self.arguments,
118                &self.features,
119            )?;
120
121            progress.step();
122            progress.set_name("list refs".into());
123            let mut remote_refs = transport
124                .invoke(
125                    Command::LsRefs.as_str(),
126                    self.features.into_iter(),
127                    if self.arguments.is_empty() {
128                        None
129                    } else {
130                        Some(self.arguments.into_iter())
131                    },
132                    trace,
133                )
134                .await?;
135            Ok(from_v2_refs(&mut remote_refs).await?)
136        }
137
138        /// Invoke a ls-refs V2 command on `transport`.
139        ///
140        /// `progress` is used to provide feedback.
141        /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate.
142        #[cfg(feature = "blocking-client")]
143        pub fn invoke_blocking(
144            self,
145            mut transport: impl blocking_io::Transport,
146            progress: &mut impl Progress,
147            trace: bool,
148        ) -> Result<Vec<Ref>, Error> {
149            let _span = gix_features::trace::detail!("gix_protocol::LsRefsCommand::invoke_blocking()");
150            Command::LsRefs.validate_argument_prefixes(
151                gix_transport::Protocol::V2,
152                self.capabilities,
153                &self.arguments,
154                &self.features,
155            )?;
156
157            progress.step();
158            progress.set_name("list refs".into());
159            let mut remote_refs = transport.invoke(
160                Command::LsRefs.as_str(),
161                self.features.into_iter(),
162                if self.arguments.is_empty() {
163                    None
164                } else {
165                    Some(self.arguments.into_iter())
166                },
167                trace,
168            )?;
169            Ok(from_v2_refs(&mut remote_refs)?)
170        }
171    }
172}