Skip to main content

gix_protocol/fetch/refmap/
init.rs

1use bstr::{BString, ByteSlice};
2use gix_transport::client::Capabilities;
3
4use crate::{
5    fetch::{
6        RefMap,
7        refmap::{Mapping, Source, SpecIndex},
8    },
9    handshake::Ref,
10};
11
12/// The error returned by [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`].
13#[derive(Debug, thiserror::Error)]
14#[allow(missing_docs)]
15pub enum Error {
16    #[error("The object format {format:?} as used by the remote is unsupported")]
17    UnknownObjectFormat { format: BString },
18    #[error(transparent)]
19    MappingValidation(#[from] gix_refspec::match_group::validate::Error),
20    #[error(transparent)]
21    ListRefs(#[from] crate::ls_refs::Error),
22}
23
24/// For use in [`RefMap::from_refs()`].
25#[derive(Debug, Clone)]
26pub struct Context {
27    /// All explicit refspecs to identify references on the remote that you are interested in.
28    /// Note that these are copied to [`RefMap::refspecs`] for convenience, as `RefMap::mappings` refer to them by index.
29    pub fetch_refspecs: Vec<gix_refspec::RefSpec>,
30    /// A list of refspecs to use as implicit refspecs which won't be saved or otherwise be part of the remote in question.
31    ///
32    /// This is useful for handling `remote.<name>.tagOpt` for example.
33    pub extra_refspecs: Vec<gix_refspec::RefSpec>,
34}
35
36impl Context {
37    pub(crate) fn aggregate_refspecs(&self) -> Vec<gix_refspec::RefSpec> {
38        let mut all_refspecs = self.fetch_refspecs.clone();
39        all_refspecs.extend(self.extra_refspecs.iter().cloned());
40        all_refspecs
41    }
42}
43
44impl RefMap {
45    /// Create a ref-map from already obtained `remote_refs`. Use `context` to pass in refspecs.
46    /// `capabilities` are used to determine the object format.
47    pub fn from_refs(remote_refs: Vec<Ref>, capabilities: &Capabilities, context: Context) -> Result<RefMap, Error> {
48        let all_refspecs = context.aggregate_refspecs();
49        let Context {
50            fetch_refspecs,
51            extra_refspecs,
52        } = context;
53        let num_explicit_specs = fetch_refspecs.len();
54        let group = gix_refspec::MatchGroup::from_fetch_specs(all_refspecs.iter().map(gix_refspec::RefSpec::to_ref));
55        let object_hash = extract_object_hash(capabilities)?;
56        let null = object_hash.null();
57        let (res, fixes) = group
58            .match_lhs(remote_refs.iter().map(|r| {
59                let (full_ref_name, target, object) = r.unpack();
60                gix_refspec::match_group::Item {
61                    full_ref_name,
62                    target: target.unwrap_or(&null),
63                    object,
64                }
65            }))
66            .validated()?;
67
68        let mappings = res.mappings;
69        let mappings = mappings
70            .into_iter()
71            .map(|m| Mapping {
72                remote: m.item_index.map_or_else(
73                    || {
74                        Source::ObjectId(match m.lhs {
75                            gix_refspec::match_group::SourceRef::ObjectId(id) => id,
76                            _ => unreachable!("no item index implies having an object id"),
77                        })
78                    },
79                    |idx| Source::Ref(remote_refs[idx].clone()),
80                ),
81                local: m.rhs.map(std::borrow::Cow::into_owned),
82                spec_index: if m.spec_index < num_explicit_specs {
83                    SpecIndex::ExplicitInRemote(m.spec_index)
84                } else {
85                    SpecIndex::Implicit(m.spec_index - num_explicit_specs)
86                },
87            })
88            .collect();
89
90        Ok(Self {
91            mappings,
92            refspecs: fetch_refspecs,
93            extra_refspecs,
94            fixes,
95            remote_refs,
96            object_hash,
97        })
98    }
99}
100
101/// Resolve the object format advertised by the server through the `object-format` capability.
102///
103/// When the capability is absent, the server is implicitly speaking Sha1 - older servers
104/// don't advertise it at all, and even newer ones may omit it for empty repositories.
105/// In builds whose `gix-hash` lacks the `sha1` feature, it's treated as unknown object format error.
106fn extract_object_hash(capabilities: &Capabilities) -> Result<gix_hash::Kind, Error> {
107    let object_format = match capabilities.capability("object-format").and_then(|c| c.value()) {
108        Some(object_format) => object_format.to_str().map_err(|_| Error::UnknownObjectFormat {
109            format: object_format.into(),
110        })?,
111        None => "sha1",
112    };
113    object_format
114        .parse::<gix_hash::Kind>()
115        .map_err(|_| Error::UnknownObjectFormat {
116            format: object_format.into(),
117        })
118}