gix_protocol/fetch/refmap/
init.rs1use std::{borrow::Cow, collections::HashSet};
2
3use bstr::{BString, ByteSlice, ByteVec};
4use gix_features::progress::Progress;
5use gix_transport::client::Capabilities;
6
7#[cfg(feature = "async-client")]
8use crate::transport::client::async_io::Transport;
9#[cfg(feature = "blocking-client")]
10use crate::transport::client::blocking_io::Transport;
11use crate::{
12 fetch::{
13 refmap::{Mapping, Source, SpecIndex},
14 RefMap,
15 },
16 handshake::Ref,
17};
18
19#[derive(Debug, thiserror::Error)]
21#[allow(missing_docs)]
22pub enum Error {
23 #[error("The object format {format:?} as used by the remote is unsupported")]
24 UnknownObjectFormat { format: BString },
25 #[error(transparent)]
26 MappingValidation(#[from] gix_refspec::match_group::validate::Error),
27 #[error(transparent)]
28 ListRefs(#[from] crate::ls_refs::Error),
29}
30
31#[derive(Debug, Clone)]
33pub struct Context {
34 pub fetch_refspecs: Vec<gix_refspec::RefSpec>,
37 pub extra_refspecs: Vec<gix_refspec::RefSpec>,
41}
42
43impl Context {
44 fn aggregate_refspecs(&self) -> Vec<gix_refspec::RefSpec> {
45 let mut all_refspecs = self.fetch_refspecs.clone();
46 all_refspecs.extend(self.extra_refspecs.iter().cloned());
47 all_refspecs
48 }
49}
50
51impl RefMap {
52 #[allow(clippy::result_large_err)]
65 #[maybe_async::maybe_async]
66 pub async fn fetch<T>(
67 mut progress: impl Progress,
68 capabilities: &Capabilities,
69 transport: &mut T,
70 user_agent: (&'static str, Option<Cow<'static, str>>),
71 trace_packetlines: bool,
72 prefix_from_spec_as_filter_on_remote: bool,
73 context: Context,
74 ) -> Result<Self, Error>
75 where
76 T: Transport,
77 {
78 let _span = gix_trace::coarse!("gix_protocol::fetch::RefMap::new()");
79 let all_refspecs = context.aggregate_refspecs();
80 let remote_refs = crate::ls_refs(
81 transport,
82 capabilities,
83 |_capabilities, arguments| {
84 push_prefix_arguments(prefix_from_spec_as_filter_on_remote, arguments, &all_refspecs);
85 Ok(crate::ls_refs::Action::Continue)
86 },
87 &mut progress,
88 trace_packetlines,
89 user_agent,
90 )
91 .await?;
92
93 Self::from_refs(remote_refs, capabilities, context)
94 }
95
96 pub fn from_refs(remote_refs: Vec<Ref>, capabilities: &Capabilities, context: Context) -> Result<RefMap, Error> {
99 let all_refspecs = context.aggregate_refspecs();
100 let Context {
101 fetch_refspecs,
102 extra_refspecs,
103 } = context;
104 let num_explicit_specs = fetch_refspecs.len();
105 let group = gix_refspec::MatchGroup::from_fetch_specs(all_refspecs.iter().map(gix_refspec::RefSpec::to_ref));
106 let null = gix_hash::ObjectId::null(gix_hash::Kind::Sha1); let (res, fixes) = group
108 .match_lhs(remote_refs.iter().map(|r| {
109 let (full_ref_name, target, object) = r.unpack();
110 gix_refspec::match_group::Item {
111 full_ref_name,
112 target: target.unwrap_or(&null),
113 object,
114 }
115 }))
116 .validated()?;
117
118 let mappings = res.mappings;
119 let mappings = mappings
120 .into_iter()
121 .map(|m| Mapping {
122 remote: m.item_index.map_or_else(
123 || {
124 Source::ObjectId(match m.lhs {
125 gix_refspec::match_group::SourceRef::ObjectId(id) => id,
126 _ => unreachable!("no item index implies having an object id"),
127 })
128 },
129 |idx| Source::Ref(remote_refs[idx].clone()),
130 ),
131 local: m.rhs.map(std::borrow::Cow::into_owned),
132 spec_index: if m.spec_index < num_explicit_specs {
133 SpecIndex::ExplicitInRemote(m.spec_index)
134 } else {
135 SpecIndex::Implicit(m.spec_index - num_explicit_specs)
136 },
137 })
138 .collect();
139
140 let object_hash = if let Some(object_format) = capabilities.capability("object-format").and_then(|c| c.value())
142 {
143 let object_format = object_format.to_str().map_err(|_| Error::UnknownObjectFormat {
144 format: object_format.into(),
145 })?;
146 match object_format {
147 "sha1" => gix_hash::Kind::Sha1,
148 unknown => return Err(Error::UnknownObjectFormat { format: unknown.into() }),
149 }
150 } else {
151 gix_hash::Kind::Sha1
152 };
153
154 Ok(Self {
155 mappings,
156 refspecs: fetch_refspecs,
157 extra_refspecs,
158 fixes,
159 remote_refs,
160 object_hash,
161 })
162 }
163}
164
165fn push_prefix_arguments(
166 prefix_from_spec_as_filter_on_remote: bool,
167 arguments: &mut Vec<BString>,
168 all_refspecs: &[gix_refspec::RefSpec],
169) {
170 if !prefix_from_spec_as_filter_on_remote {
171 return;
172 }
173
174 let mut seen = HashSet::new();
175 for spec in all_refspecs {
176 let spec = spec.to_ref();
177 if seen.insert(spec.instruction()) {
178 let mut prefixes = Vec::with_capacity(1);
179 spec.expand_prefixes(&mut prefixes);
180 for mut prefix in prefixes {
181 prefix.insert_str(0, "ref-prefix ");
182 arguments.push(prefix);
183 }
184 }
185 }
186}