115 lines · 4.5 KB · Rust
RawDownload
use bstr::ByteSlice;use gix_error::{ErrorExt, ResultExt, ValidationError, message};use gix_transport::client::Capabilities; use crate::{    fetch::{        RefMap,        refmap::{Mapping, Source, SpecIndex},    },    handshake::Ref,}; /// For use in [`RefMap::from_refs()`].#[derive(Debug, Clone)]pub struct Context {    /// All explicit refspecs to identify references on the remote that you are interested in.    /// Note that these are copied to [`RefMap::refspecs`] for convenience, as `RefMap::mappings` refer to them by index.    pub fetch_refspecs: Vec<gix_refspec::RefSpec>,    /// 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 Context {    pub(crate) fn aggregate_refspecs(&self) -> Vec<gix_refspec::RefSpec> {        let mut all_refspecs = self.fetch_refspecs.clone();        all_refspecs.extend(self.extra_refspecs.iter().cloned());        all_refspecs    }} impl RefMap {    /// Create a ref-map from already obtained `remote_refs`. Use `context` to pass in refspecs.    /// `capabilities` are used to determine the object format.    pub fn from_refs(        remote_refs: Vec<Ref>,        capabilities: &Capabilities,        context: Context,    ) -> Result<RefMap, gix_error::Exn> {        let all_refspecs = context.aggregate_refspecs();        let Context {            fetch_refspecs,            extra_refspecs,        } = context;        let num_explicit_specs = fetch_refspecs.len();        let group = gix_refspec::MatchGroup::from_fetch_specs(all_refspecs.iter().map(gix_refspec::RefSpec::to_ref));        let object_hash = extract_object_hash(capabilities)?;        let null = object_hash.null();        let (res, fixes) = group            .match_lhs(remote_refs.iter().map(|r| {                let (full_ref_name, target, object) = r.unpack();                gix_refspec::match_group::Item {                    full_ref_name,                    target: target.unwrap_or(&null),                    object,                }            }))            .validated()            .or_raise_erased(|| message("Failed to validate mappings between remote and local references"))?;         let mappings = res.mappings;        let mappings = mappings            .into_iter()            .map(|m| Mapping {                remote: m.item_index.map_or_else(                    || {                        Source::ObjectId(match m.lhs {                            gix_refspec::match_group::SourceRef::ObjectId(id) => id,                            _ => unreachable!("no item index implies having an object id"),                        })                    },                    |idx| Source::Ref(remote_refs[idx].clone()),                ),                local: m.rhs.map(std::borrow::Cow::into_owned),                spec_index: if m.spec_index < num_explicit_specs {                    SpecIndex::ExplicitInRemote(m.spec_index)                } else {                    SpecIndex::Implicit(m.spec_index - num_explicit_specs)                },            })            .collect();         Ok(Self {            mappings,            refspecs: fetch_refspecs,            extra_refspecs,            fixes,            remote_refs,            object_hash,        })    }} /// Resolve the object format advertised by the server through the `object-format` capability.////// When the capability is absent, the server is implicitly speaking Sha1 - older servers/// don't advertise it at all, and even newer ones may omit it for empty repositories./// In builds whose `gix-hash` lacks the `sha1` feature, it's treated as unknown object format error.fn extract_object_hash(capabilities: &Capabilities) -> Result<gix_hash::Kind, gix_error::Exn> {    let object_format = match capabilities.capability("object-format").and_then(|c| c.value()) {        Some(object_format) => object_format.to_str().or_raise_erased(|| {            ValidationError::new_with_input("The object format used by the remote isn't valid UTF-8", object_format)        })?,        None => "sha1",    };    match object_format.parse::<gix_hash::Kind>() {        Ok(kind) => Ok(kind),        Err(err) => Err(ValidationError::new_with_input(            format!("The object format used by the remote is unsupported: {err}"),            object_format,        )        .raise_erased()),    }}