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<dyn std::error::Error + Send + Sync + 'static>), #[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<String>)>, /// 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.<name>.tagOpt` for example. pub extra_refspecs: Vec<gix_refspec::RefSpec>,} 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<T> 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<T> 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<fetch::RefMap, Error> { 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) }}
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<dyn std::error::Error + Send + Sync + 'static>),
#[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<String>)>,
/// 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.<name>.tagOpt` for example.
pub extra_refspecs: Vec<gix_refspec::RefSpec>,
}
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<T> 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<T> 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<fetch::RefMap, Error> {
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)
}
}