use std::path::PathBuf; use crate::fetch::response::{Acknowledgement, ShallowUpdate, WantedRef}; /// Options for use in [`fetch()`](`crate::fetch()`)#[derive(Debug, Clone)]pub struct Options<'a> { /// The path to the file containing the shallow commit boundary. /// /// When needed, it will be locked in preparation for being modified. pub shallow_file: PathBuf, /// How to deal with shallow repositories. It does affect how negotiations are performed. pub shallow: &'a Shallow, /// Describe how to handle tags when fetching. pub tags: Tags, /// If `true`, if we fetch from a remote that only offers shallow clones, the operation will fail with an error /// instead of writing the shallow boundary to the shallow file. pub reject_shallow_remote: bool,} /// For use in [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`] and [`fetch`](crate::fetch()).#[cfg(feature = "handshake")]pub struct Context<'a, T> { /// The outcome of the handshake performed with the remote. /// /// Note that it's mutable as depending on the protocol, it may contain refs that have been sent unconditionally. pub handshake: &'a mut crate::Handshake, /// The transport to use when making an `ls-refs` or `fetch` call. /// /// This is always done if the underlying protocol is V2, which is implied by the absence of refs in the `handshake` outcome. pub transport: &'a mut T, /// How to self-identify during the `ls-refs` call in [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`] or the `fetch` call in [`fetch()`](crate::fetch()). /// /// This could be read from the `gitoxide.userAgent` configuration variable. pub user_agent: crate::command::Feature, /// If `true`, output all packetlines using the `gix-trace` machinery. pub trace_packetlines: bool,} #[cfg(feature = "fetch")]mod with_fetch { use bstr::ByteSlice; use crate::fetch::{self, negotiate, refmap}; /// For use in [`fetch`](crate::fetch()). pub struct NegotiateContext<'a, 'b, 'c, Objects, Alternates, AlternatesOut, AlternatesErr, Find> where Objects: gix_object::Find + gix_object::FindHeader + gix_object::Exists, Alternates: FnOnce() -> Result<AlternatesOut, AlternatesErr>, AlternatesErr: Into<Box<dyn std::error::Error + Send + Sync + 'static>>, AlternatesOut: Iterator<Item = (gix_ref::file::Store, Find)>, Find: gix_object::Find, { /// Access to the object database. /// *Note* that the `exists()` calls must not trigger a refresh of the ODB packs as plenty of them might fail, i.e. find on object. pub objects: &'a Objects, /// Access to the git references database. pub refs: &'a gix_ref::file::Store, /// A function that returns an iterator over `(refs, objects)` for each alternate repository, to assure all known objects are added also according to their tips. pub alternates: Alternates, /// The implementation that performs the negotiation later, i.e. prepare wants and haves. pub negotiator: &'a mut dyn gix_negotiate::Negotiator, /// The commit-graph for use by the `negotiator` - we populate it with tips to initialize the graph traversal. pub graph: &'a mut gix_negotiate::Graph<'b, 'c>, } /// A trait to encapsulate steps to negotiate the contents of the pack. /// /// Typical implementations use the utilities found in the [`negotiate`] module. pub trait Negotiate { /// Typically invokes [`negotiate::mark_complete_and_common_ref()`]. fn mark_complete_and_common_ref(&mut self) -> Result<negotiate::Action, negotiate::Error>; /// Typically invokes [`negotiate::add_wants()`]. /// Returns `true` if wants were added, or `false` if the negotiation should be aborted. #[must_use] fn add_wants(&mut self, arguments: &mut fetch::Arguments, remote_ref_target_known: &[bool]) -> bool; /// Typically invokes [`negotiate::one_round()`]. fn one_round( &mut self, state: &mut negotiate::one_round::State, arguments: &mut fetch::Arguments, previous_response: Option<&fetch::Response>, ) -> Result<(negotiate::Round, bool), negotiate::Error>; } /// The outcome of [`fetch()`](crate::fetch()). #[derive(Debug, Clone)] pub struct Outcome { /// The most recent server response. /// /// Useful to obtain information about new shallow boundaries. pub last_response: fetch::Response, /// Information about the negotiation to receive the new pack. pub negotiate: NegotiateOutcome, } /// The negotiation-specific outcome of [`fetch()`](crate::fetch()). #[derive(Debug, Clone)] pub struct NegotiateOutcome { /// The outcome of the negotiation stage of the fetch operation. /// /// If it is… /// /// * [`negotiate::Action::MustNegotiate`] there will always be a `pack`. /// * [`negotiate::Action::SkipToRefUpdate`] there is no `pack` but references can be updated right away. /// /// Note that this is never [negotiate::Action::NoChange`] as this would mean there is no negotiation information at all /// so this structure wouldn't be present. pub action: negotiate::Action, /// Additional information for each round of negotiation. pub rounds: Vec<negotiate::Round>, } /// Information about the relationship between our refspecs, and remote references with their local counterparts. /// /// It's the first stage that offers connection to the server, and is typically required to perform one or more fetch operations. #[derive(Default, Debug, Clone)] pub struct RefMap { /// A mapping between a remote reference and a local tracking branch. pub mappings: Vec<refmap::Mapping>, /// The explicit refspecs that were supposed to be used for fetching. /// /// Typically, they are configured by the remote and are referred to by /// [`refmap::SpecIndex::ExplicitInRemote`] in [`refmap::Mapping`]. pub refspecs: Vec<gix_refspec::RefSpec>, /// Refspecs which have been added implicitly due to settings of the `remote`, usually pre-initialized from /// [`extra_refspecs` in RefMap options](refmap::init::Context). /// They are referred to by [`refmap::SpecIndex::Implicit`] in [`refmap::Mapping`]. /// /// They are never persisted nor are they typically presented to the user. pub extra_refspecs: Vec<gix_refspec::RefSpec>, /// Information about the fixes applied to the `mapping` due to validation and sanitization. pub fixes: Vec<gix_refspec::match_group::validate::Fix>, /// All refs advertised by the remote. pub remote_refs: Vec<crate::handshake::Ref>, /// The kind of hash used for all data sent by the server, if understood by this client implementation. /// /// It was extracted from the `handshake` as advertised by the server. pub object_hash: gix_hash::Kind, } impl RefMap { /// Return `true` if the explicit fetch refspecs represented by this mapping failed to match the remote in a way /// that should typically be reported as "no mapping". /// /// Use this before negotiation or reference updates when callers need to reject a fetch early instead of proceeding /// with an empty or purely implicit mapping set. /// /// This is the case if the server advertised refs but none matched at all, or if only implicit mappings were produced /// while at least one explicit refspec required an actual ref match, as is the case for exact ref names like `HEAD` /// or `refs/heads/main`. pub fn is_missing_required_mapping(&self) -> bool { let has_explicit_mapping = self .mappings .iter() .any(|mapping| matches!(mapping.spec_index, crate::fetch::refmap::SpecIndex::ExplicitInRemote(_))); (self.mappings.is_empty() && !self.remote_refs.is_empty()) || (!has_explicit_mapping && explicit_fetch_refspecs_require_a_match(&self.refspecs)) } } fn explicit_fetch_refspecs_require_a_match(refspecs: &[gix_refspec::RefSpec]) -> bool { refspecs.iter().any(|spec| match spec.to_ref().instruction() { gix_refspec::Instruction::Fetch( gix_refspec::instruction::Fetch::Only { src } | gix_refspec::instruction::Fetch::AndUpdate { src, .. }, ) => src.find_byteset(b"*?[]\\").is_none() && gix_hash::ObjectId::from_hex(src).is_err(), gix_refspec::Instruction::Fetch(gix_refspec::instruction::Fetch::Exclude { .. }) | gix_refspec::Instruction::Push(_) => false, }) }}#[cfg(feature = "fetch")]pub use with_fetch::*; /// Describe how shallow clones are handled when fetching, with variants defining how the *shallow boundary* is handled.////// The *shallow boundary* is a set of commits whose parents are not present in the repository.#[derive(Default, Debug, Clone, PartialEq, Eq)]pub enum Shallow { /// Fetch all changes from the remote without affecting the shallow boundary at all. /// /// This also means that repositories that aren't shallow will remain like that. #[default] NoChange, /// Receive update to `depth` commits in the history of the refs to fetch (from the viewpoint of the remote), /// with the value of `1` meaning to receive only the commit a ref is pointing to. /// /// This may update the shallow boundary to increase or decrease the amount of available history. DepthAtRemote(std::num::NonZeroU32), /// Increase the number of commits and thus expand the shallow boundary by `depth` commits as seen from our local /// shallow boundary, with a value of `0` having no effect. Deepen(u32), /// Set the shallow boundary at the `cutoff` time, meaning that there will be no commits beyond that time. Since { /// The date beyond which there will be no history. cutoff: gix_date::Time, }, /// Receive all history excluding all commits reachable from `remote_refs`. These can be long or short /// ref names or tag names. Exclude { /// The ref names to exclude, short or long. Note that ambiguous short names will cause the remote to abort /// without an error message being transferred (because the protocol does not support it) remote_refs: Vec<gix_ref::PartialName>, /// If some, this field has the same meaning as [`Shallow::Since`] which can be used in combination /// with excluded references. since_cutoff: Option<gix_date::Time>, },} impl Shallow { /// Produce a variant that causes the repository to loose its shallow boundary, effectively by extending it /// beyond all limits. pub fn undo() -> Self { Shallow::DepthAtRemote((i32::MAX as u32).try_into().expect("valid at compile time")) }} /// Describe how to handle tags when fetching#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]pub enum Tags { /// Fetch all tags from the remote, even if these are not reachable from objects referred to by our refspecs. All, /// Fetch only the tags that point to the objects being sent. /// That way, annotated tags that point to an object we receive are automatically transmitted and their refs are created. /// The same goes for lightweight tags. #[default] Included, /// Do not fetch any tags. None,} impl Tags { /// Obtain a refspec that determines whether or not to fetch all tags, depending on this variant. /// /// The returned refspec is the default refspec for tags, but won't overwrite local tags ever. #[cfg(feature = "fetch")] pub fn to_refspec(&self) -> Option<gix_refspec::RefSpecRef<'static>> { match self { Tags::All | Tags::Included => Some( gix_refspec::parse("refs/tags/*:refs/tags/*".into(), gix_refspec::parse::Operation::Fetch) .expect("valid"), ), Tags::None => None, } }} /// A representation of a complete fetch response#[derive(Debug, Clone)]pub struct Response { pub(crate) acks: Vec<Acknowledgement>, pub(crate) shallows: Vec<ShallowUpdate>, pub(crate) wanted_refs: Vec<WantedRef>, pub(crate) has_pack: bool,} /// The progress ids used in during various steps of the fetch operation.////// Note that tagged progress isn't very widely available yet, but support can be improved as needed.////// Use this information to selectively extract the progress of interest in case the parent application has custom visualization.#[derive(Debug, Copy, Clone)]pub enum ProgressId { /// The progress name is defined by the remote and the progress messages it sets, along with their progress values and limits. RemoteProgress,} impl From<ProgressId> for gix_features::progress::Id { fn from(v: ProgressId) -> Self { match v { ProgressId::RemoteProgress => *b"FERP", } }}
use std::path::PathBuf;
use crate::fetch::response::{Acknowledgement, ShallowUpdate, WantedRef};
/// Options for use in [`fetch()`](`crate::fetch()`)
#[derive(Debug, Clone)]
pub struct Options<'a> {
/// The path to the file containing the shallow commit boundary.
///
/// When needed, it will be locked in preparation for being modified.
pub shallow_file: PathBuf,
/// How to deal with shallow repositories. It does affect how negotiations are performed.
pub shallow: &'a Shallow,
/// Describe how to handle tags when fetching.
pub tags: Tags,
/// If `true`, if we fetch from a remote that only offers shallow clones, the operation will fail with an error
/// instead of writing the shallow boundary to the shallow file.
pub reject_shallow_remote: bool,
}
/// For use in [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`] and [`fetch`](crate::fetch()).
#[cfg(feature = "handshake")]
pub struct Context<'a, T> {
/// The outcome of the handshake performed with the remote.
///
/// Note that it's mutable as depending on the protocol, it may contain refs that have been sent unconditionally.
pub handshake: &'a mut crate::Handshake,
/// The transport to use when making an `ls-refs` or `fetch` call.
///
/// This is always done if the underlying protocol is V2, which is implied by the absence of refs in the `handshake` outcome.
pub transport: &'a mut T,
/// How to self-identify during the `ls-refs` call in [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`] or the `fetch` call in [`fetch()`](crate::fetch()).
///
/// This could be read from the `gitoxide.userAgent` configuration variable.
pub user_agent: crate::command::Feature,
/// If `true`, output all packetlines using the `gix-trace` machinery.
pub trace_packetlines: bool,
}
#[cfg(feature = "fetch")]
mod with_fetch {
use bstr::ByteSlice;
use crate::fetch::{self, negotiate, refmap};
/// For use in [`fetch`](crate::fetch()).
pub struct NegotiateContext<'a, 'b, 'c, Objects, Alternates, AlternatesOut, AlternatesErr, Find>
where
Objects: gix_object::Find + gix_object::FindHeader + gix_object::Exists,
Alternates: FnOnce() -> Result<AlternatesOut, AlternatesErr>,
AlternatesErr: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
AlternatesOut: Iterator<Item = (gix_ref::file::Store, Find)>,
Find: gix_object::Find,
{
/// Access to the object database.
/// *Note* that the `exists()` calls must not trigger a refresh of the ODB packs as plenty of them might fail, i.e. find on object.
pub objects: &'a Objects,
/// Access to the git references database.
pub refs: &'a gix_ref::file::Store,
/// A function that returns an iterator over `(refs, objects)` for each alternate repository, to assure all known objects are added also according to their tips.
pub alternates: Alternates,
/// The implementation that performs the negotiation later, i.e. prepare wants and haves.
pub negotiator: &'a mut dyn gix_negotiate::Negotiator,
/// The commit-graph for use by the `negotiator` - we populate it with tips to initialize the graph traversal.
pub graph: &'a mut gix_negotiate::Graph<'b, 'c>,
}
/// A trait to encapsulate steps to negotiate the contents of the pack.
///
/// Typical implementations use the utilities found in the [`negotiate`] module.
pub trait Negotiate {
/// Typically invokes [`negotiate::mark_complete_and_common_ref()`].
fn mark_complete_and_common_ref(&mut self) -> Result<negotiate::Action, negotiate::Error>;
/// Typically invokes [`negotiate::add_wants()`].
/// Returns `true` if wants were added, or `false` if the negotiation should be aborted.
#[must_use]
fn add_wants(&mut self, arguments: &mut fetch::Arguments, remote_ref_target_known: &[bool]) -> bool;
/// Typically invokes [`negotiate::one_round()`].
fn one_round(
&mut self,
state: &mut negotiate::one_round::State,
arguments: &mut fetch::Arguments,
previous_response: Option<&fetch::Response>,
) -> Result<(negotiate::Round, bool), negotiate::Error>;
}
/// The outcome of [`fetch()`](crate::fetch()).
#[derive(Debug, Clone)]
pub struct Outcome {
/// The most recent server response.
///
/// Useful to obtain information about new shallow boundaries.
pub last_response: fetch::Response,
/// Information about the negotiation to receive the new pack.
pub negotiate: NegotiateOutcome,
}
/// The negotiation-specific outcome of [`fetch()`](crate::fetch()).
#[derive(Debug, Clone)]
pub struct NegotiateOutcome {
/// The outcome of the negotiation stage of the fetch operation.
///
/// If it is…
///
/// * [`negotiate::Action::MustNegotiate`] there will always be a `pack`.
/// * [`negotiate::Action::SkipToRefUpdate`] there is no `pack` but references can be updated right away.
///
/// Note that this is never [negotiate::Action::NoChange`] as this would mean there is no negotiation information at all
/// so this structure wouldn't be present.
pub action: negotiate::Action,
/// Additional information for each round of negotiation.
pub rounds: Vec<negotiate::Round>,
}
/// Information about the relationship between our refspecs, and remote references with their local counterparts.
///
/// It's the first stage that offers connection to the server, and is typically required to perform one or more fetch operations.
#[derive(Default, Debug, Clone)]
pub struct RefMap {
/// A mapping between a remote reference and a local tracking branch.
pub mappings: Vec<refmap::Mapping>,
/// The explicit refspecs that were supposed to be used for fetching.
///
/// Typically, they are configured by the remote and are referred to by
/// [`refmap::SpecIndex::ExplicitInRemote`] in [`refmap::Mapping`].
pub refspecs: Vec<gix_refspec::RefSpec>,
/// Refspecs which have been added implicitly due to settings of the `remote`, usually pre-initialized from
/// [`extra_refspecs` in RefMap options](refmap::init::Context).
/// They are referred to by [`refmap::SpecIndex::Implicit`] in [`refmap::Mapping`].
///
/// They are never persisted nor are they typically presented to the user.
pub extra_refspecs: Vec<gix_refspec::RefSpec>,
/// Information about the fixes applied to the `mapping` due to validation and sanitization.
pub fixes: Vec<gix_refspec::match_group::validate::Fix>,
/// All refs advertised by the remote.
pub remote_refs: Vec<crate::handshake::Ref>,
/// The kind of hash used for all data sent by the server, if understood by this client implementation.
///
/// It was extracted from the `handshake` as advertised by the server.
pub object_hash: gix_hash::Kind,
}
impl RefMap {
/// Return `true` if the explicit fetch refspecs represented by this mapping failed to match the remote in a way
/// that should typically be reported as "no mapping".
///
/// Use this before negotiation or reference updates when callers need to reject a fetch early instead of proceeding
/// with an empty or purely implicit mapping set.
///
/// This is the case if the server advertised refs but none matched at all, or if only implicit mappings were produced
/// while at least one explicit refspec required an actual ref match, as is the case for exact ref names like `HEAD`
/// or `refs/heads/main`.
pub fn is_missing_required_mapping(&self) -> bool {
let has_explicit_mapping = self
.mappings
.iter()
.any(|mapping| matches!(mapping.spec_index, crate::fetch::refmap::SpecIndex::ExplicitInRemote(_)));
(self.mappings.is_empty() && !self.remote_refs.is_empty())
|| (!has_explicit_mapping && explicit_fetch_refspecs_require_a_match(&self.refspecs))
}
}
fn explicit_fetch_refspecs_require_a_match(refspecs: &[gix_refspec::RefSpec]) -> bool {
refspecs.iter().any(|spec| match spec.to_ref().instruction() {
gix_refspec::Instruction::Fetch(
gix_refspec::instruction::Fetch::Only { src } | gix_refspec::instruction::Fetch::AndUpdate { src, .. },
) => src.find_byteset(b"*?[]\\").is_none() && gix_hash::ObjectId::from_hex(src).is_err(),
gix_refspec::Instruction::Fetch(gix_refspec::instruction::Fetch::Exclude { .. })
| gix_refspec::Instruction::Push(_) => false,
})
}
}
#[cfg(feature = "fetch")]
pub use with_fetch::*;
/// Describe how shallow clones are handled when fetching, with variants defining how the *shallow boundary* is handled.
///
/// The *shallow boundary* is a set of commits whose parents are not present in the repository.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub enum Shallow {
/// Fetch all changes from the remote without affecting the shallow boundary at all.
///
/// This also means that repositories that aren't shallow will remain like that.
#[default]
NoChange,
/// Receive update to `depth` commits in the history of the refs to fetch (from the viewpoint of the remote),
/// with the value of `1` meaning to receive only the commit a ref is pointing to.
///
/// This may update the shallow boundary to increase or decrease the amount of available history.
DepthAtRemote(std::num::NonZeroU32),
/// Increase the number of commits and thus expand the shallow boundary by `depth` commits as seen from our local
/// shallow boundary, with a value of `0` having no effect.
Deepen(u32),
/// Set the shallow boundary at the `cutoff` time, meaning that there will be no commits beyond that time.
Since {
/// The date beyond which there will be no history.
cutoff: gix_date::Time,
},
/// Receive all history excluding all commits reachable from `remote_refs`. These can be long or short
/// ref names or tag names.
Exclude {
/// The ref names to exclude, short or long. Note that ambiguous short names will cause the remote to abort
/// without an error message being transferred (because the protocol does not support it)
remote_refs: Vec<gix_ref::PartialName>,
/// If some, this field has the same meaning as [`Shallow::Since`] which can be used in combination
/// with excluded references.
since_cutoff: Option<gix_date::Time>,
},
}
impl Shallow {
/// Produce a variant that causes the repository to loose its shallow boundary, effectively by extending it
/// beyond all limits.
pub fn undo() -> Self {
Shallow::DepthAtRemote((i32::MAX as u32).try_into().expect("valid at compile time"))
}
}
/// Describe how to handle tags when fetching
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub enum Tags {
/// Fetch all tags from the remote, even if these are not reachable from objects referred to by our refspecs.
All,
/// Fetch only the tags that point to the objects being sent.
/// That way, annotated tags that point to an object we receive are automatically transmitted and their refs are created.
/// The same goes for lightweight tags.
#[default]
Included,
/// Do not fetch any tags.
None,
}
impl Tags {
/// Obtain a refspec that determines whether or not to fetch all tags, depending on this variant.
///
/// The returned refspec is the default refspec for tags, but won't overwrite local tags ever.
#[cfg(feature = "fetch")]
pub fn to_refspec(&self) -> Option<gix_refspec::RefSpecRef<'static>> {
match self {
Tags::All | Tags::Included => Some(
gix_refspec::parse("refs/tags/*:refs/tags/*".into(), gix_refspec::parse::Operation::Fetch)
.expect("valid"),
),
Tags::None => None,
}
}
}
/// A representation of a complete fetch response
#[derive(Debug, Clone)]
pub struct Response {
pub(crate) acks: Vec<Acknowledgement>,
pub(crate) shallows: Vec<ShallowUpdate>,
pub(crate) wanted_refs: Vec<WantedRef>,
pub(crate) has_pack: bool,
}
/// The progress ids used in during various steps of the fetch operation.
///
/// Note that tagged progress isn't very widely available yet, but support can be improved as needed.
///
/// Use this information to selectively extract the progress of interest in case the parent application has custom visualization.
#[derive(Debug, Copy, Clone)]
pub enum ProgressId {
/// The progress name is defined by the remote and the progress messages it sets, along with their progress values and limits.
RemoteProgress,
}
impl From<ProgressId> for gix_features::progress::Id {
fn from(v: ProgressId) -> Self {
match v {
ProgressId::RemoteProgress => *b"FERP",
}
}
}