use gix_features::progress::Progress; #[cfg(feature = "async-network-client")] use gix_transport::client::async_io::Transport; #[cfg(feature = "blocking-network-client")] use gix_transport::client::blocking_io::Transport; use crate::{ bstr::BString, remote::{Connection, connection::ConnectionDetached, fetch}, }; /// The error returned by [`Connection::ref_map()`]. #[derive(Debug, thiserror::Error)] #[expect(missing_docs)] pub enum Error { #[error(transparent)] InitRefMap(#[from] gix_protocol::fetch::refmap::init::Error), #[error("Failed to configure the transport before connecting to {url:?}")] GatherTransportConfig { url: BString, source: crate::config::transport::Error, }, #[error("Failed to configure the transport layer")] ConfigureTransport(#[from] Box), #[error(transparent)] Handshake(#[from] gix_protocol::handshake::Error), #[error(transparent)] Transport(#[from] gix_protocol::transport::client::Error), #[error(transparent)] ConfigureCredentials(#[from] crate::config::credential_helpers::Error), } impl gix_protocol::transport::IsSpuriousError for Error { fn is_spurious(&self) -> bool { match self { Error::Transport(err) => err.is_spurious(), Error::Handshake(err) => err.is_spurious(), _ => false, } } } /// For use in [`Connection::ref_map()`]. #[derive(Debug, Clone)] pub struct Options { /// Use a two-component prefix derived from the ref-spec's source, like `refs/heads/` to let the server pre-filter refs /// with great potential for savings in traffic and local CPU time. Defaults to `true`. pub prefix_from_spec_as_filter_on_remote: bool, /// Parameters in the form of `(name, optional value)` to add to the handshake. /// /// This is useful in case of custom servers. pub handshake_parameters: Vec<(String, Option)>, /// A list of refspecs to use as implicit refspecs which won't be saved or otherwise be part of the remote in question. /// /// This is useful for handling `remote..tagOpt` for example. pub extra_refspecs: Vec, } impl Default for Options { fn default() -> Self { Options { prefix_from_spec_as_filter_on_remote: true, handshake_parameters: Vec::new(), extra_refspecs: Vec::new(), } } } impl Connection<'_, '_, '_, T> where T: Transport, { /// List all references on the remote that have been filtered through our remote's [`refspecs`][crate::Remote::refspecs()] /// for _fetching_. /// /// This comes in the form of all matching tips on the remote and the object they point to, along with /// the local tracking branch of these tips (if available). /// /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository. /// /// # Consumption /// /// Due to management of the transport, it's cleanest to only use it for a single interaction. Thus, it's consumed /// along with the connection. /// /// ### Configuration /// /// - `gitoxide.userAgent` is read to obtain the application user agent for git servers and for HTTP servers as well. #[gix_protocol::bisync::bisync] pub async fn ref_map( self, progress: impl Progress, options: Options, ) -> Result<(fetch::RefMap, gix_protocol::Handshake), Error> { let repo = self.remote.repo; self.into_detached().ref_map(repo, progress, options).await } } impl ConnectionDetached<'_, T> where T: Transport, { #[gix_protocol::bisync::bisync] pub(crate) async fn ref_map( mut self, repo: &crate::Repository, progress: impl Progress, options: Options, ) -> Result<(fetch::RefMap, gix_protocol::Handshake), Error> { let refmap = self.ref_map_by_ref(repo, progress, options).await?; let handshake = self .handshake .expect("refmap always performs handshake and stores it if it succeeds"); Ok((refmap, handshake)) } #[gix_protocol::bisync::bisync] pub(crate) async fn ref_map_by_ref( &mut self, repo: &crate::Repository, mut progress: impl Progress, Options { prefix_from_spec_as_filter_on_remote, handshake_parameters, mut extra_refspecs, }: Options, ) -> Result { let _span = gix_trace::coarse!("remote::Connection::ref_map()"); if let Some(tag_spec) = self.remote.fetch_tags.to_refspec().map(|spec| spec.to_owned()) && !extra_refspecs.contains(&tag_spec) { extra_refspecs.push(tag_spec); } let mut credentials_storage; let url = self.transport.inner.to_url(); let authenticate = match self.authenticate.as_mut() { Some(f) => f, None => { credentials_storage = self.configured_credentials_for_current_url(repo); &mut credentials_storage } }; if self.transport_options.is_none() { self.transport_options = repo .transport_options(url.as_ref(), self.remote.name().map(crate::remote::Name::as_bstr)) .map_err(|err| Error::GatherTransportConfig { source: err, url: url.into_owned(), })?; } if let Some(config) = self.transport_options.as_ref() { self.transport.inner.configure(&**config)?; } let mut handshake = gix_protocol::handshake( &mut self.transport.inner, gix_transport::Service::UploadPack, authenticate, handshake_parameters, &mut progress, ) .await?; let context = fetch::refmap::init::Context { fetch_refspecs: self.remote.fetch_specs.clone(), extra_refspecs, }; let fetch_refmap = handshake.prepare_lsrefs_or_extract_refmap( repo.config.user_agent_tuple(), prefix_from_spec_as_filter_on_remote, context, )?; #[cfg(feature = "async-network-client")] let ref_map = fetch_refmap .fetch_async(progress, &mut self.transport.inner, self.trace) .await?; #[cfg(feature = "blocking-network-client")] let ref_map = fetch_refmap.fetch_blocking(progress, &mut self.transport.inner, self.trace)?; self.handshake = Some(handshake); Ok(ref_map) } }