use bstr::BString; ///pub mod refs; /// A git reference, commonly referred to as 'ref', as returned by a git server before sending a pack.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]pub enum Ref { /// A ref pointing to a `tag` object, which in turns points to an `object`, usually a commit Peeled { /// The name at which the ref is located, like `refs/tags/1.0`. full_ref_name: BString, /// The hash of the tag the ref points to. tag: gix_hash::ObjectId, /// The hash of the object the `tag` points to. object: gix_hash::ObjectId, }, /// A ref pointing to a commit object Direct { /// The name at which the ref is located, like `refs/heads/main` or `refs/tags/v1.0` for lightweight tags. full_ref_name: BString, /// The hash of the object the ref points to. object: gix_hash::ObjectId, }, /// A symbolic ref pointing to `target` ref, which in turn, ultimately after possibly following `tag`, points to an `object` Symbolic { /// The name at which the symbolic ref is located, like `HEAD`. full_ref_name: BString, /// The path of the ref the symbolic ref points to, like `refs/heads/main`. /// /// See issue [#205] for details /// /// [#205]: https://github.com/GitoxideLabs/gitoxide/issues/205 target: BString, /// The hash of the annotated tag the ref points to, if present. /// /// Note that this field is also `None` if `full_ref_name` is a lightweight tag. tag: Option<gix_hash::ObjectId>, /// The hash of the object the `target` ref ultimately points to. object: gix_hash::ObjectId, }, /// A ref is unborn on the remote and just points to the initial, unborn branch, as is the case in a newly initialized repository /// or dangling symbolic refs. Unborn { /// The name at which the ref is located, typically `HEAD`. full_ref_name: BString, /// The path of the ref the symbolic ref points to, like `refs/heads/main`, even though the `target` does not yet exist. target: BString, },} #[cfg(feature = "handshake")]pub(crate) mod hero { use crate::handshake::Ref; /// The result of the [`handshake()`](crate::handshake()) function. #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Handshake { /// The protocol version the server responded with. It might have downgraded the desired version. pub server_protocol_version: gix_transport::Protocol, /// The references reported as part of the `Protocol::V1` handshake, or `None` otherwise as V2 requires a separate request. pub refs: Option<Vec<Ref>>, /// Shallow updates as part of the `Protocol::V1`, to shallow a particular object. /// Note that unshallowing isn't supported here. pub v1_shallow_updates: Option<Vec<crate::fetch::response::ShallowUpdate>>, /// The server capabilities. pub capabilities: gix_transport::client::Capabilities, } #[cfg(feature = "fetch")] mod fetch { use crate::{Handshake, command::Feature, fetch::RefMap, ls_refs::RefPrefixes}; use gix_features::progress::Progress; /// Intermediate state while potentially fetching a refmap after the handshake. pub enum ObtainRefMap<'a> { /// We already got a refmap to use from the V1 handshake, which always sends them. Existing(RefMap), /// We need to invoke another `ls-refs` command to retrieve the refmap as part of the V2 protocol. LsRefsCommand(crate::LsRefsCommand<'a>, crate::fetch::refmap::init::Context), } macro_rules! fetch { ($name:ident, $bisync:path, $transport:path, $invoke:ident, $mode:literal) => { /// Fetch the refmap, either by returning the existing one or invoking the `ls-refs` command. #[$bisync] pub async fn $name( self, mut progress: impl Progress, transport: &mut impl $transport, trace_packetlines: bool, ) -> Result<RefMap, crate::fetch::refmap::init::Error> { let (cmd, cx) = match self { ObtainRefMap::Existing(map) => return Ok(map), ObtainRefMap::LsRefsCommand(cmd, cx) => (cmd, cx), }; let _span = gix_trace::coarse!( "gix_protocol::handshake::ObtainRefMap::fetch()", mode = $mode ); let capabilities = cmd.capabilities; let remote_refs = cmd .$invoke(transport, &mut progress, trace_packetlines) .await?; RefMap::from_refs(remote_refs, capabilities, cx) } }; } impl ObtainRefMap<'_> { #[cfg(feature = "async-client")] fetch!( fetch_async, ::bisync::asynchronous::bisync, crate::transport::client::async_io::Transport, invoke_async, "async" ); #[cfg(feature = "blocking-client")] fetch!( fetch_blocking, ::bisync::synchronous::bisync, crate::transport::client::blocking_io::Transport, invoke_blocking, "blocking" ); } impl Handshake { /// Prepare fetching a [refmap](RefMap) if not present in the handshake. pub fn prepare_lsrefs_or_extract_refmap( &mut self, user_agent: Feature, prefix_from_spec_as_filter_on_remote: bool, refmap_context: crate::fetch::refmap::init::Context, ) -> Result<ObtainRefMap<'_>, crate::fetch::refmap::init::Error> { if let Some(refs) = self.refs.take() { return Ok(ObtainRefMap::Existing(RefMap::from_refs( refs, &self.capabilities, refmap_context, )?)); } let prefix_refs = prefix_from_spec_as_filter_on_remote.then(|| { let all_refspecs = refmap_context.aggregate_refspecs(); RefPrefixes::from_refspecs(&all_refspecs) }); Ok(ObtainRefMap::LsRefsCommand( crate::LsRefsCommand::new(prefix_refs, &self.capabilities, user_agent), refmap_context, )) } } }} #[cfg(feature = "handshake")]mod error { use bstr::BString; use gix_transport::client; use crate::{credentials, handshake::refs}; /// The error returned by [`handshake()`][crate::handshake()]. #[derive(Debug, thiserror::Error)] #[expect(missing_docs)] pub enum Error { #[error("Failed to obtain credentials")] Credentials(#[from] credentials::protocol::Error), #[error("No credentials were returned at all as if the credential helper isn't functioning unknowingly")] EmptyCredentials, #[error("Credentials provided for \"{url}\" were not accepted by the remote")] InvalidCredentials { url: BString, source: std::io::Error }, #[error(transparent)] Transport(#[from] client::Error), #[error( "The transport didn't accept the advertised server version {actual_version:?} and closed the connection client side" )] TransportProtocolPolicyViolation { actual_version: gix_transport::Protocol }, #[error(transparent)] ParseRefs(#[from] refs::parse::Error), } impl gix_transport::IsSpuriousError for Error { fn is_spurious(&self) -> bool { match self { Error::Transport(err) => err.is_spurious(), _ => false, } } }} #[cfg(feature = "handshake")]pub use error::Error; #[cfg(any(feature = "blocking-client", feature = "async-client"))]#[cfg(feature = "handshake")]pub(crate) mod function;
use bstr::BString;
///
pub mod refs;
/// A git reference, commonly referred to as 'ref', as returned by a git server before sending a pack.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Ref {
/// A ref pointing to a `tag` object, which in turns points to an `object`, usually a commit
Peeled {
/// The name at which the ref is located, like `refs/tags/1.0`.
full_ref_name: BString,
/// The hash of the tag the ref points to.
tag: gix_hash::ObjectId,
/// The hash of the object the `tag` points to.
object: gix_hash::ObjectId,
},
/// A ref pointing to a commit object
Direct {
/// The name at which the ref is located, like `refs/heads/main` or `refs/tags/v1.0` for lightweight tags.
full_ref_name: BString,
/// The hash of the object the ref points to.
object: gix_hash::ObjectId,
},
/// A symbolic ref pointing to `target` ref, which in turn, ultimately after possibly following `tag`, points to an `object`
Symbolic {
/// The name at which the symbolic ref is located, like `HEAD`.
full_ref_name: BString,
/// The path of the ref the symbolic ref points to, like `refs/heads/main`.
///
/// See issue [#205] for details
///
/// [#205]: https://github.com/GitoxideLabs/gitoxide/issues/205
target: BString,
/// The hash of the annotated tag the ref points to, if present.
///
/// Note that this field is also `None` if `full_ref_name` is a lightweight tag.
tag: Option<gix_hash::ObjectId>,
/// The hash of the object the `target` ref ultimately points to.
object: gix_hash::ObjectId,
},
/// A ref is unborn on the remote and just points to the initial, unborn branch, as is the case in a newly initialized repository
/// or dangling symbolic refs.
Unborn {
/// The name at which the ref is located, typically `HEAD`.
full_ref_name: BString,
/// The path of the ref the symbolic ref points to, like `refs/heads/main`, even though the `target` does not yet exist.
target: BString,
},
}
#[cfg(feature = "handshake")]
pub(crate) mod hero {
use crate::handshake::Ref;
/// The result of the [`handshake()`](crate::handshake()) function.
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Handshake {
/// The protocol version the server responded with. It might have downgraded the desired version.
pub server_protocol_version: gix_transport::Protocol,
/// The references reported as part of the `Protocol::V1` handshake, or `None` otherwise as V2 requires a separate request.
pub refs: Option<Vec<Ref>>,
/// Shallow updates as part of the `Protocol::V1`, to shallow a particular object.
/// Note that unshallowing isn't supported here.
pub v1_shallow_updates: Option<Vec<crate::fetch::response::ShallowUpdate>>,
/// The server capabilities.
pub capabilities: gix_transport::client::Capabilities,
}
#[cfg(feature = "fetch")]
mod fetch {
use crate::{Handshake, command::Feature, fetch::RefMap, ls_refs::RefPrefixes};
use gix_features::progress::Progress;
/// Intermediate state while potentially fetching a refmap after the handshake.
pub enum ObtainRefMap<'a> {
/// We already got a refmap to use from the V1 handshake, which always sends them.
Existing(RefMap),
/// We need to invoke another `ls-refs` command to retrieve the refmap as part of the V2 protocol.
LsRefsCommand(crate::LsRefsCommand<'a>, crate::fetch::refmap::init::Context),
}
macro_rules! fetch {
($name:ident, $bisync:path, $transport:path, $invoke:ident, $mode:literal) => {
/// Fetch the refmap, either by returning the existing one or invoking the `ls-refs` command.
#[$bisync]
pub async fn $name(
self,
mut progress: impl Progress,
transport: &mut impl $transport,
trace_packetlines: bool,
) -> Result<RefMap, crate::fetch::refmap::init::Error> {
let (cmd, cx) = match self {
ObtainRefMap::Existing(map) => return Ok(map),
ObtainRefMap::LsRefsCommand(cmd, cx) => (cmd, cx),
};
let _span = gix_trace::coarse!(
"gix_protocol::handshake::ObtainRefMap::fetch()",
mode = $mode
);
let capabilities = cmd.capabilities;
let remote_refs = cmd
.$invoke(transport, &mut progress, trace_packetlines)
.await?;
RefMap::from_refs(remote_refs, capabilities, cx)
}
};
}
impl ObtainRefMap<'_> {
#[cfg(feature = "async-client")]
fetch!(
fetch_async,
::bisync::asynchronous::bisync,
crate::transport::client::async_io::Transport,
invoke_async,
"async"
);
#[cfg(feature = "blocking-client")]
fetch!(
fetch_blocking,
::bisync::synchronous::bisync,
crate::transport::client::blocking_io::Transport,
invoke_blocking,
"blocking"
);
}
impl Handshake {
/// Prepare fetching a [refmap](RefMap) if not present in the handshake.
pub fn prepare_lsrefs_or_extract_refmap(
&mut self,
user_agent: Feature,
prefix_from_spec_as_filter_on_remote: bool,
refmap_context: crate::fetch::refmap::init::Context,
) -> Result<ObtainRefMap<'_>, crate::fetch::refmap::init::Error> {
if let Some(refs) = self.refs.take() {
return Ok(ObtainRefMap::Existing(RefMap::from_refs(
refs,
&self.capabilities,
refmap_context,
)?));
}
let prefix_refs = prefix_from_spec_as_filter_on_remote.then(|| {
let all_refspecs = refmap_context.aggregate_refspecs();
RefPrefixes::from_refspecs(&all_refspecs)
});
Ok(ObtainRefMap::LsRefsCommand(
crate::LsRefsCommand::new(prefix_refs, &self.capabilities, user_agent),
refmap_context,
))
}
}
}
}
#[cfg(feature = "handshake")]
mod error {
use bstr::BString;
use gix_transport::client;
use crate::{credentials, handshake::refs};
/// The error returned by [`handshake()`][crate::handshake()].
#[derive(Debug, thiserror::Error)]
#[expect(missing_docs)]
pub enum Error {
#[error("Failed to obtain credentials")]
Credentials(#[from] credentials::protocol::Error),
#[error("No credentials were returned at all as if the credential helper isn't functioning unknowingly")]
EmptyCredentials,
#[error("Credentials provided for \"{url}\" were not accepted by the remote")]
InvalidCredentials { url: BString, source: std::io::Error },
#[error(transparent)]
Transport(#[from] client::Error),
#[error(
"The transport didn't accept the advertised server version {actual_version:?} and closed the connection client side"
)]
TransportProtocolPolicyViolation { actual_version: gix_transport::Protocol },
#[error(transparent)]
ParseRefs(#[from] refs::parse::Error),
}
impl gix_transport::IsSpuriousError for Error {
fn is_spurious(&self) -> bool {
match self {
Error::Transport(err) => err.is_spurious(),
_ => false,
}
}
}
}
#[cfg(feature = "handshake")]
pub use error::Error;
#[cfg(any(feature = "blocking-client", feature = "async-client"))]
#[cfg(feature = "handshake")]
pub(crate) mod function;