1#[cfg(any(feature = "blocking-client", feature = "async-client"))]
2mod error {
3 use crate::handshake::refs::parse;
4
5 #[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 use self::function::RefPrefixes;
34
35#[cfg(any(feature = "blocking-client", feature = "async-client"))]
36pub(crate) mod function {
37 use std::{borrow::Cow, collections::HashSet};
38
39 use bstr::{BString, ByteVec};
40 use gix_features::progress::Progress;
41 use gix_transport::client::Capabilities;
42
43 use super::Error;
44 #[cfg(feature = "async-client")]
45 use crate::transport::client::async_io::{self, TransportV2Ext as _};
46 #[cfg(feature = "blocking-client")]
47 use crate::transport::client::blocking_io::{self, TransportV2Ext as _};
48 use crate::{
49 handshake::{refs::from_v2_refs, Ref},
50 Command,
51 };
52
53 pub struct RefPrefixes {
68 prefixes: Vec<BString>,
69 }
70
71 impl Default for RefPrefixes {
72 fn default() -> Self {
73 Self::new()
74 }
75 }
76
77 impl RefPrefixes {
78 pub fn new() -> RefPrefixes {
80 RefPrefixes { prefixes: Vec::new() }
81 }
82
83 pub fn from_refspecs<'a>(refspecs: impl IntoIterator<Item = &'a gix_refspec::RefSpec>) -> Self {
95 let mut seen = HashSet::new();
96 let mut prefixes = Self::new();
97 for spec in refspecs.into_iter() {
98 let spec = spec.to_ref();
99 if seen.insert(spec.instruction()) {
100 let mut out = Vec::with_capacity(1);
101 spec.expand_prefixes(&mut out);
102 prefixes.extend(out);
103 }
104 }
105 prefixes
106 }
107
108 fn into_args(self) -> impl Iterator<Item = BString> {
109 self.prefixes.into_iter().map(|mut prefix| {
110 prefix.insert_str(0, "ref-prefix ");
111 prefix
112 })
113 }
114 }
115
116 impl Extend<BString> for RefPrefixes {
117 fn extend<T: IntoIterator<Item = BString>>(&mut self, iter: T) {
118 for prefix in iter {
119 if !self.prefixes.iter().any(|existing| existing == &prefix) {
120 self.prefixes.push(prefix);
121 }
122 }
123 }
124 }
125
126 pub struct LsRefsCommand<'a> {
131 pub(crate) capabilities: &'a Capabilities,
132 features: Vec<(&'static str, Option<Cow<'static, str>>)>,
133 arguments: Vec<BString>,
134 }
135
136 impl<'a> LsRefsCommand<'a> {
137 pub fn new(
143 ref_prefixes: Option<RefPrefixes>,
144 capabilities: &'a Capabilities,
145 agent: (&'static str, Option<Cow<'static, str>>),
146 ) -> Self {
147 let ls_refs = Command::LsRefs;
148 let mut features = ls_refs.default_features(gix_transport::Protocol::V2, capabilities);
149 features.push(agent);
150 let mut arguments = ls_refs.initial_v2_arguments(&features);
151 if capabilities
152 .capability("ls-refs")
153 .and_then(|cap| cap.supports("unborn"))
154 .unwrap_or_default()
155 {
156 arguments.push("unborn".into());
157 }
158
159 if let Some(prefixes) = ref_prefixes {
160 arguments.extend(prefixes.into_args());
161 }
162
163 Self {
164 capabilities,
165 features,
166 arguments,
167 }
168 }
169
170 #[cfg(feature = "async-client")]
175 pub async fn invoke_async(
176 self,
177 mut transport: impl async_io::Transport,
178 progress: &mut impl Progress,
179 trace: bool,
180 ) -> Result<Vec<Ref>, Error> {
181 let _span = gix_features::trace::detail!("gix_protocol::LsRefsCommand::invoke_async()");
182 Command::LsRefs.validate_argument_prefixes(
183 gix_transport::Protocol::V2,
184 self.capabilities,
185 &self.arguments,
186 &self.features,
187 )?;
188
189 progress.step();
190 progress.set_name("list refs".into());
191 let mut remote_refs = transport
192 .invoke(
193 Command::LsRefs.as_str(),
194 self.features.into_iter(),
195 if self.arguments.is_empty() {
196 None
197 } else {
198 Some(self.arguments.into_iter())
199 },
200 trace,
201 )
202 .await?;
203 Ok(from_v2_refs(&mut remote_refs).await?)
204 }
205
206 #[cfg(feature = "blocking-client")]
211 pub fn invoke_blocking(
212 self,
213 mut transport: impl blocking_io::Transport,
214 progress: &mut impl Progress,
215 trace: bool,
216 ) -> Result<Vec<Ref>, Error> {
217 let _span = gix_features::trace::detail!("gix_protocol::LsRefsCommand::invoke_blocking()");
218 Command::LsRefs.validate_argument_prefixes(
219 gix_transport::Protocol::V2,
220 self.capabilities,
221 &self.arguments,
222 &self.features,
223 )?;
224
225 progress.step();
226 progress.set_name("list refs".into());
227 let mut remote_refs = transport.invoke(
228 Command::LsRefs.as_str(),
229 self.features.into_iter(),
230 if self.arguments.is_empty() {
231 None
232 } else {
233 Some(self.arguments.into_iter())
234 },
235 trace,
236 )?;
237 Ok(from_v2_refs(&mut remote_refs)?)
238 }
239 }
240
241 #[cfg(test)]
242 mod ref_prefixes {
243 use bstr::{BString, ByteSlice};
244
245 use super::RefPrefixes;
246
247 #[test]
248 fn extend_preserves_first_seen_order_and_deduplicates_prefixes() {
249 let mut prefixes = RefPrefixes::new();
250 prefixes.extend(
251 [
252 "refs/tags",
253 "HEAD",
254 "main",
255 "refs/heads/main",
256 "refs/tags",
257 "HEAD",
258 "refs/heads/feature",
259 "refs/heads/main",
260 ]
261 .into_iter()
262 .map(|prefix| prefix.as_bytes().as_bstr().to_owned()),
263 );
264
265 assert_eq!(
266 prefixes.into_args().collect::<Vec<_>>(),
267 [
268 "ref-prefix refs/tags",
269 "ref-prefix HEAD",
270 "ref-prefix main",
271 "ref-prefix refs/heads/main",
272 "ref-prefix refs/heads/feature"
273 ]
274 .into_iter()
275 .map(BString::from)
276 .collect::<Vec<_>>()
277 );
278 }
279
280 #[test]
281 fn from_refspecs_keeps_exact_refs_and_dwim_expansions() {
282 let specs = [
283 gix_refspec::parse("HEAD".into(), gix_refspec::parse::Operation::Fetch)
284 .expect("valid")
285 .to_owned(),
286 gix_refspec::parse("dwim".into(), gix_refspec::parse::Operation::Fetch)
287 .expect("valid")
288 .to_owned(),
289 gix_refspec::parse(
290 "refs/tags/prefix*:refs/tags/prefix*".into(),
291 gix_refspec::parse::Operation::Fetch,
292 )
293 .expect("valid")
294 .to_owned(),
295 gix_refspec::parse("refs/heads/main".into(), gix_refspec::parse::Operation::Fetch)
296 .expect("valid")
297 .to_owned(),
298 ];
299
300 let prefixes = RefPrefixes::from_refspecs(&specs);
301
302 assert_eq!(
303 prefixes.into_args().collect::<Vec<_>>(),
304 [
305 "ref-prefix HEAD",
306 "ref-prefix dwim",
307 "ref-prefix refs/dwim",
308 "ref-prefix refs/tags/dwim",
309 "ref-prefix refs/heads/dwim",
310 "ref-prefix refs/remotes/dwim",
311 "ref-prefix refs/remotes/dwim/HEAD",
312 "ref-prefix refs/tags/prefix",
313 "ref-prefix refs/heads/main",
314 ]
315 .into_iter()
316 .map(BString::from)
317 .collect::<Vec<_>>()
318 );
319 }
320 }
321}