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