#[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::{ Progress, bstr::BString, remote, remote::{ Connection, connection::ConnectionDetached, fetch::{DryRun, RefMap}, ref_map, },}; mod error;pub use error::Error; use crate::remote::fetch::WritePackedRefs; /// The way reflog messages should be composed whenever a ref is written with recent objects from a remote.pub enum RefLogMessage { /// Prefix the log with `action` and generate the typical suffix as `git` would. Prefixed { /// The action to use, like `fetch` or `pull`. action: String, }, /// Control the entire message, using `message` verbatim. Override { /// The complete reflog message. message: BString, },} impl RefLogMessage { pub(crate) fn compose(&self, context: &str) -> BString { match self { RefLogMessage::Prefixed { action } => format!("{action}: {context}").into(), RefLogMessage::Override { message } => message.to_owned(), } }} /// The status of the repository after the fetch operation#[derive(Debug, Clone)]pub enum Status { /// Nothing changed as the remote didn't have anything new compared to our tracking branches, thus no pack was received /// and no new object was added. /// /// As we could determine that nothing changed without remote interaction, there was no negotiation at all. NoPackReceived { /// If `true`, we didn't receive a pack due to dry-run mode being enabled. dry_run: bool, /// Information about the pack negotiation phase if negotiation happened at all. /// /// It's possible that negotiation didn't have to happen as no reference of interest changed on the server. negotiate: Option<outcome::Negotiate>, /// However, depending on the refspecs, references might have been updated nonetheless to point to objects as /// reported by the remote. update_refs: refs::update::Outcome, }, /// There was at least one tip with a new object which we received. Change { /// Information about the pack negotiation phase. negotiate: outcome::Negotiate, /// Information collected while writing the pack and its index. write_pack_bundle: gix_pack::bundle::write::Outcome, /// Information collected while updating references. update_refs: refs::update::Outcome, },} /// The outcome of receiving a pack via [`Prepare::receive()`].#[derive(Debug, Clone)]pub struct Outcome { /// The result of the initial mapping of references, the prerequisite for any fetch. pub ref_map: RefMap, /// The outcome of the handshake with the server. pub handshake: gix_protocol::Handshake, /// The status of the operation to indicate what happened. pub status: Status,} /// Additional types related to the outcome of a fetch operation.pub mod outcome { /// Information about the negotiation phase of a fetch. /// /// Note that negotiation can happen even if no pack is ultimately produced. #[derive(Default, Debug, Clone)] pub struct Negotiate { /// The negotiation graph indicating what kind of information 'the algorithm' collected in the end. pub graph: gix_negotiate::IdMap, /// Additional information for each round of negotiation. pub rounds: Vec<gix_protocol::fetch::negotiate::Round>, }} pub use gix_protocol::fetch::ProgressId; ///pub mod prepare { /// The error returned by [`prepare_fetch()`][super::Connection::prepare_fetch()]. #[derive(Debug, thiserror::Error)] #[expect(missing_docs)] pub enum Error { #[error("Cannot perform a meaningful fetch operation without any configured ref-specs")] MissingRefSpecs, #[error(transparent)] RefMap(#[from] crate::remote::ref_map::Error), } impl gix_protocol::transport::IsSpuriousError for Error { fn is_spurious(&self) -> bool { match self { Error::RefMap(err) => err.is_spurious(), _ => false, } } }} impl<'auth, 'repo, T> Connection<'_, 'auth, 'repo, T>where T: Transport,{ /// Perform a handshake with the remote and obtain a ref-map with `options`, and from there one /// Note that at this point, the `transport` should already be configured using the [`transport_mut()`][Self::transport_mut()] /// method, as it will be consumed here. /// /// From there additional properties of the fetch can be adjusted to override the defaults that are configured via git-config. /// /// # Async Experimental /// /// Note that this implementation is currently limited correctly in blocking mode only as it relies on Drop semantics to close the connection /// should the fetch not be performed. Furthermore, there the code doing the fetch is inherently blocking and it's not offloaded to a thread, /// making this call block the executor. /// It's best to unblock it by placing it into its own thread or offload it should usage in an async context be truly required. #[gix_protocol::bisync::bisync] pub async fn prepare_fetch( self, progress: impl Progress, options: ref_map::Options, ) -> Result<Prepare<'auth, 'repo, T>, prepare::Error> { let repo = self.remote.repo; let inner = self.into_detached().prepare_fetch(repo, progress, options).await?; Ok(Prepare { inner, repo }) }} impl<'remote, T> ConnectionDetached<'remote, T>where T: Transport,{ #[gix_protocol::bisync::bisync] pub(crate) async fn prepare_fetch( mut self, repo: &crate::Repository, progress: impl Progress, options: ref_map::Options, ) -> Result<PrepareDetached<'remote, T>, prepare::Error> { if self.remote.fetch_refspecs().is_empty() && options.extra_refspecs.is_empty() { return Err(prepare::Error::MissingRefSpecs); } let ref_map = self.ref_map_by_ref(repo, progress, options).await?; Ok(PrepareDetached { con: Some(self), ref_map, dry_run: DryRun::No, reflog_message: None, write_packed_refs: WritePackedRefs::Never, shallow: Default::default(), }) }} impl<T> Prepare<'_, '_, T>where T: Transport,{ /// Return the `ref_map` (that includes the server handshake) which was part of listing refs prior to fetching a pack. pub fn ref_map(&self) -> &RefMap { &self.inner.ref_map }} impl<T> PrepareDetached<'_, T>where T: Transport,{ /// Return the `ref_map` (that includes the server handshake) which was part of listing refs prior to fetching a pack. pub(crate) fn ref_map(&self) -> &RefMap { &self.ref_map }} mod config;mod receive_pack;///#[path = "update_refs/mod.rs"]pub mod refs; /// A structure to hold the result of the handshake with the remote and configure the upcoming fetch operation.pub struct Prepare<'remote, 'repo, T>where T: Transport,{ inner: PrepareDetached<'remote, T>, repo: &'repo crate::Repository,} /// A repository-detached preparation for a fetch operation.pub(crate) struct PrepareDetached<'remote, T>where T: Transport,{ con: Option<ConnectionDetached<'remote, T>>, ref_map: RefMap, dry_run: DryRun, reflog_message: Option<RefLogMessage>, write_packed_refs: WritePackedRefs, shallow: remote::fetch::Shallow,} /// Builderimpl<T> Prepare<'_, '_, T>where T: Transport,{ /// If dry run is enabled, no change to the repository will be made. /// /// This works by not actually fetching the pack after negotiating it, nor will refs be updated. pub fn with_dry_run(mut self, enabled: bool) -> Self { self.inner.dry_run = if enabled { DryRun::Yes } else { DryRun::No }; self } /// If enabled, don't write ref updates to loose refs, but put them exclusively to packed-refs. /// /// This improves performance and allows case-sensitive filesystems to deal with ref names that would otherwise /// collide. pub fn with_write_packed_refs_only(mut self, enabled: bool) -> Self { let changed = self.inner.with_write_packed_refs_only(enabled); self.inner = changed; self } /// Set the reflog message to use when updating refs after fetching a pack. pub fn with_reflog_message(mut self, reflog_message: RefLogMessage) -> Self { self.inner.reflog_message = reflog_message.into(); self } /// Define what to do when the current repository is a shallow clone. /// /// *Has no effect if the current repository is not as shallow clone.* pub fn with_shallow(mut self, shallow: remote::fetch::Shallow) -> Self { self.inner.shallow = shallow; self }} /// Builderimpl<T> PrepareDetached<'_, T>where T: Transport,{ pub(crate) fn with_write_packed_refs_only(mut self, enabled: bool) -> Self { self.write_packed_refs = if enabled { WritePackedRefs::Only } else { WritePackedRefs::Never }; self } pub(crate) fn with_reflog_message(mut self, reflog_message: RefLogMessage) -> Self { self.reflog_message = reflog_message.into(); self } pub(crate) fn with_shallow(mut self, shallow: remote::fetch::Shallow) -> Self { self.shallow = shallow; self }}
#[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::{
Progress,
bstr::BString,
remote,
remote::{
Connection,
connection::ConnectionDetached,
fetch::{DryRun, RefMap},
ref_map,
},
};
mod error;
pub use error::Error;
use crate::remote::fetch::WritePackedRefs;
/// The way reflog messages should be composed whenever a ref is written with recent objects from a remote.
pub enum RefLogMessage {
/// Prefix the log with `action` and generate the typical suffix as `git` would.
Prefixed {
/// The action to use, like `fetch` or `pull`.
action: String,
},
/// Control the entire message, using `message` verbatim.
Override {
/// The complete reflog message.
message: BString,
},
}
impl RefLogMessage {
pub(crate) fn compose(&self, context: &str) -> BString {
match self {
RefLogMessage::Prefixed { action } => format!("{action}: {context}").into(),
RefLogMessage::Override { message } => message.to_owned(),
}
}
}
/// The status of the repository after the fetch operation
#[derive(Debug, Clone)]
pub enum Status {
/// Nothing changed as the remote didn't have anything new compared to our tracking branches, thus no pack was received
/// and no new object was added.
///
/// As we could determine that nothing changed without remote interaction, there was no negotiation at all.
NoPackReceived {
/// If `true`, we didn't receive a pack due to dry-run mode being enabled.
dry_run: bool,
/// Information about the pack negotiation phase if negotiation happened at all.
///
/// It's possible that negotiation didn't have to happen as no reference of interest changed on the server.
negotiate: Option<outcome::Negotiate>,
/// However, depending on the refspecs, references might have been updated nonetheless to point to objects as
/// reported by the remote.
update_refs: refs::update::Outcome,
},
/// There was at least one tip with a new object which we received.
Change {
/// Information about the pack negotiation phase.
negotiate: outcome::Negotiate,
/// Information collected while writing the pack and its index.
write_pack_bundle: gix_pack::bundle::write::Outcome,
/// Information collected while updating references.
update_refs: refs::update::Outcome,
},
}
/// The outcome of receiving a pack via [`Prepare::receive()`].
#[derive(Debug, Clone)]
pub struct Outcome {
/// The result of the initial mapping of references, the prerequisite for any fetch.
pub ref_map: RefMap,
/// The outcome of the handshake with the server.
pub handshake: gix_protocol::Handshake,
/// The status of the operation to indicate what happened.
pub status: Status,
}
/// Additional types related to the outcome of a fetch operation.
pub mod outcome {
/// Information about the negotiation phase of a fetch.
///
/// Note that negotiation can happen even if no pack is ultimately produced.
#[derive(Default, Debug, Clone)]
pub struct Negotiate {
/// The negotiation graph indicating what kind of information 'the algorithm' collected in the end.
pub graph: gix_negotiate::IdMap,
/// Additional information for each round of negotiation.
pub rounds: Vec<gix_protocol::fetch::negotiate::Round>,
}
}
pub use gix_protocol::fetch::ProgressId;
///
pub mod prepare {
/// The error returned by [`prepare_fetch()`][super::Connection::prepare_fetch()].
#[derive(Debug, thiserror::Error)]
#[expect(missing_docs)]
pub enum Error {
#[error("Cannot perform a meaningful fetch operation without any configured ref-specs")]
MissingRefSpecs,
#[error(transparent)]
RefMap(#[from] crate::remote::ref_map::Error),
}
impl gix_protocol::transport::IsSpuriousError for Error {
fn is_spurious(&self) -> bool {
match self {
Error::RefMap(err) => err.is_spurious(),
_ => false,
}
}
}
}
impl<'auth, 'repo, T> Connection<'_, 'auth, 'repo, T>
where
T: Transport,
{
/// Perform a handshake with the remote and obtain a ref-map with `options`, and from there one
/// Note that at this point, the `transport` should already be configured using the [`transport_mut()`][Self::transport_mut()]
/// method, as it will be consumed here.
///
/// From there additional properties of the fetch can be adjusted to override the defaults that are configured via git-config.
///
/// # Async Experimental
///
/// Note that this implementation is currently limited correctly in blocking mode only as it relies on Drop semantics to close the connection
/// should the fetch not be performed. Furthermore, there the code doing the fetch is inherently blocking and it's not offloaded to a thread,
/// making this call block the executor.
/// It's best to unblock it by placing it into its own thread or offload it should usage in an async context be truly required.
#[gix_protocol::bisync::bisync]
pub async fn prepare_fetch(
self,
progress: impl Progress,
options: ref_map::Options,
) -> Result<Prepare<'auth, 'repo, T>, prepare::Error> {
let repo = self.remote.repo;
let inner = self.into_detached().prepare_fetch(repo, progress, options).await?;
Ok(Prepare { inner, repo })
}
}
impl<'remote, T> ConnectionDetached<'remote, T>
where
T: Transport,
{
#[gix_protocol::bisync::bisync]
pub(crate) async fn prepare_fetch(
mut self,
repo: &crate::Repository,
progress: impl Progress,
options: ref_map::Options,
) -> Result<PrepareDetached<'remote, T>, prepare::Error> {
if self.remote.fetch_refspecs().is_empty() && options.extra_refspecs.is_empty() {
return Err(prepare::Error::MissingRefSpecs);
}
let ref_map = self.ref_map_by_ref(repo, progress, options).await?;
Ok(PrepareDetached {
con: Some(self),
ref_map,
dry_run: DryRun::No,
reflog_message: None,
write_packed_refs: WritePackedRefs::Never,
shallow: Default::default(),
})
}
}
impl<T> Prepare<'_, '_, T>
where
T: Transport,
{
/// Return the `ref_map` (that includes the server handshake) which was part of listing refs prior to fetching a pack.
pub fn ref_map(&self) -> &RefMap {
&self.inner.ref_map
}
}
impl<T> PrepareDetached<'_, T>
where
T: Transport,
{
/// Return the `ref_map` (that includes the server handshake) which was part of listing refs prior to fetching a pack.
pub(crate) fn ref_map(&self) -> &RefMap {
&self.ref_map
}
}
mod config;
mod receive_pack;
///
#[path = "update_refs/mod.rs"]
pub mod refs;
/// A structure to hold the result of the handshake with the remote and configure the upcoming fetch operation.
pub struct Prepare<'remote, 'repo, T>
where
T: Transport,
{
inner: PrepareDetached<'remote, T>,
repo: &'repo crate::Repository,
}
/// A repository-detached preparation for a fetch operation.
pub(crate) struct PrepareDetached<'remote, T>
where
T: Transport,
{
con: Option<ConnectionDetached<'remote, T>>,
ref_map: RefMap,
dry_run: DryRun,
reflog_message: Option<RefLogMessage>,
write_packed_refs: WritePackedRefs,
shallow: remote::fetch::Shallow,
}
/// Builder
impl<T> Prepare<'_, '_, T>
where
T: Transport,
{
/// If dry run is enabled, no change to the repository will be made.
///
/// This works by not actually fetching the pack after negotiating it, nor will refs be updated.
pub fn with_dry_run(mut self, enabled: bool) -> Self {
self.inner.dry_run = if enabled { DryRun::Yes } else { DryRun::No };
self
}
/// If enabled, don't write ref updates to loose refs, but put them exclusively to packed-refs.
///
/// This improves performance and allows case-sensitive filesystems to deal with ref names that would otherwise
/// collide.
pub fn with_write_packed_refs_only(mut self, enabled: bool) -> Self {
let changed = self.inner.with_write_packed_refs_only(enabled);
self.inner = changed;
self
}
/// Set the reflog message to use when updating refs after fetching a pack.
pub fn with_reflog_message(mut self, reflog_message: RefLogMessage) -> Self {
self.inner.reflog_message = reflog_message.into();
self
}
/// Define what to do when the current repository is a shallow clone.
///
/// *Has no effect if the current repository is not as shallow clone.*
pub fn with_shallow(mut self, shallow: remote::fetch::Shallow) -> Self {
self.inner.shallow = shallow;
self
}
}
/// Builder
impl<T> PrepareDetached<'_, T>
where
T: Transport,
{
pub(crate) fn with_write_packed_refs_only(mut self, enabled: bool) -> Self {
self.write_packed_refs = if enabled {
WritePackedRefs::Only
} else {
WritePackedRefs::Never
};
self
}
pub(crate) fn with_reflog_message(mut self, reflog_message: RefLogMessage) -> Self {
self.reflog_message = reflog_message.into();
self
}
pub(crate) fn with_shallow(mut self, shallow: remote::fetch::Shallow) -> Self {
self.shallow = shallow;
self
}
}