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