1use crate::{
2 bstr::{BString, ByteSlice},
3 clone::PrepareFetch,
4};
5use gix_ref::Category;
6
7#[derive(Debug, thiserror::Error)]
9#[allow(missing_docs)]
10pub enum Error {
11 #[error(transparent)]
12 Connect(#[from] crate::remote::connect::Error),
13 #[error(transparent)]
14 PrepareFetch(#[from] crate::remote::fetch::prepare::Error),
15 #[error(transparent)]
16 Fetch(#[from] crate::remote::fetch::Error),
17 #[error(transparent)]
18 RemoteInit(#[from] crate::remote::init::Error),
19 #[error("Custom configuration of remote to clone from failed")]
20 RemoteConfiguration(#[source] Box<dyn std::error::Error + Send + Sync>),
21 #[error("Custom configuration of connection to use when cloning failed")]
22 RemoteConnection(#[source] Box<dyn std::error::Error + Send + Sync>),
23 #[error(transparent)]
24 RemoteName(#[from] crate::config::remote::symbolic_name::Error),
25 #[error(transparent)]
26 ParseConfig(#[from] crate::config::overrides::Error),
27 #[error(transparent)]
28 ApplyConfig(#[from] crate::config::Error),
29 #[error("Failed to load repo-local git configuration before writing")]
30 LoadConfig(#[from] gix_config::file::init::from_paths::Error),
31 #[error("Failed to store configured remote in memory")]
32 SaveConfig(#[from] crate::remote::save::AsError),
33 #[error("Failed to write repository configuration to disk")]
34 SaveConfigIo(#[from] std::io::Error),
35 #[error("The remote HEAD points to a reference named {head_ref_name:?} which is invalid.")]
36 InvalidHeadRef {
37 source: gix_validate::reference::name::Error,
38 head_ref_name: crate::bstr::BString,
39 },
40 #[error("Failed to update HEAD with values from remote")]
41 HeadUpdate(#[from] crate::reference::edit::Error),
42 #[error("The remote didn't have any ref that matched '{}'", wanted.as_ref().as_bstr())]
43 RefNameMissing { wanted: gix_ref::PartialName },
44 #[error("The remote has {} refs for '{}', try to use a specific name: {}", candidates.len(), wanted.as_ref().as_bstr(), candidates.iter().filter_map(|n| n.to_str().ok()).collect::<Vec<_>>().join(", "))]
45 RefNameAmbiguous {
46 wanted: gix_ref::PartialName,
47 candidates: Vec<BString>,
48 },
49 #[error(transparent)]
50 CommitterOrFallback(#[from] crate::config::time::Error),
51 #[error(transparent)]
52 RefMap(#[from] crate::remote::ref_map::Error),
53 #[error(transparent)]
54 ReferenceName(#[from] gix_validate::reference::name::Error),
55}
56
57impl PrepareFetch {
59 #[gix_protocol::maybe_async::maybe_async]
74 pub async fn fetch_only<P>(
75 &mut self,
76 mut progress: P,
77 should_interrupt: &std::sync::atomic::AtomicBool,
78 ) -> Result<(crate::Repository, crate::remote::fetch::Outcome), Error>
79 where
80 P: crate::NestedProgress,
81 P::SubProgress: 'static,
82 {
83 use crate::{bstr::ByteVec, remote, remote::fetch::RefLogMessage};
84
85 let repo = self
86 .repo
87 .as_mut()
88 .expect("user error: multiple calls are allowed only until it succeeds");
89
90 repo.committer_or_set_generic_fallback()?;
91
92 if !self.config_overrides.is_empty() {
93 let mut snapshot = repo.config_snapshot_mut();
94 snapshot.append_config(&self.config_overrides, gix_config::Source::Api)?;
95 }
96
97 let remote_name = match self.remote_name.as_ref() {
98 Some(name) => name.to_owned(),
99 None => repo
100 .config
101 .resolved
102 .string(crate::config::tree::Clone::DEFAULT_REMOTE_NAME)
103 .map(|n| crate::config::tree::Clone::DEFAULT_REMOTE_NAME.try_into_symbolic_name(n))
104 .transpose()?
105 .unwrap_or_else(|| "origin".into()),
106 };
107
108 let mut remote = repo.remote_at(self.url.clone())?;
109
110 let use_single_branch_for_shallow = self.shallow != remote::fetch::Shallow::NoChange
113 && remote.fetch_specs.is_empty()
114 && self.fetch_options.extra_refspecs.is_empty();
115
116 let target_ref = if use_single_branch_for_shallow {
117 if let Some(ref_name) = &self.ref_name {
119 Some(Category::LocalBranch.to_full_name(ref_name.as_ref().as_bstr())?)
120 } else {
121 let mut connection = remote.connect(remote::Direction::Fetch).await?;
124 if let Some(f) = self.configure_connection.as_mut() {
125 f(&mut connection).map_err(Error::RemoteConnection)?;
126 }
127
128 let _ = connection.ref_map_by_ref(&mut progress, Default::default()).await?;
130
131 let target = if let Some(handshake) = &connection.handshake {
132 handshake.refs.as_ref().and_then(|refs| {
134 refs.iter().find_map(|r| match r {
135 gix_protocol::handshake::Ref::Symbolic {
136 full_ref_name, target, ..
137 } if full_ref_name == "HEAD" => gix_ref::FullName::try_from(target).ok(),
138 _ => None,
139 })
140 })
141 } else {
142 None
143 };
144
145 let fallback_branch = target
147 .or_else(|| {
148 repo.config
149 .resolved
150 .string(crate::config::tree::Init::DEFAULT_BRANCH)
151 .and_then(|name| Category::LocalBranch.to_full_name(name.as_bstr()).ok())
152 })
153 .unwrap_or_else(|| gix_ref::FullName::try_from("refs/heads/main").expect("known to be valid"));
154
155 drop(connection);
157
158 Some(fallback_branch)
159 }
160 } else {
161 None
162 };
163
164 if remote.fetch_specs.is_empty() {
167 if let Some(target_ref) = &target_ref {
168 let short_name = target_ref.shorten();
170 remote = remote
171 .with_refspecs(
172 Some(format!("+{target_ref}:refs/remotes/{remote_name}/{short_name}").as_str()),
173 remote::Direction::Fetch,
174 )
175 .expect("valid refspec");
176 } else {
177 remote = remote
179 .with_refspecs(
180 Some(format!("+refs/heads/*:refs/remotes/{remote_name}/*").as_str()),
181 remote::Direction::Fetch,
182 )
183 .expect("valid static spec");
184 }
185 }
186
187 let mut clone_fetch_tags = None;
188 if let Some(f) = self.configure_remote.as_mut() {
189 remote = f(remote).map_err(Error::RemoteConfiguration)?;
190 } else {
191 clone_fetch_tags = remote::fetch::Tags::All.into();
192 }
193
194 let config = util::write_remote_to_local_config_file(&mut remote, remote_name.clone())?;
195
196 if let Some(fetch_tags) = clone_fetch_tags {
198 remote = remote.with_fetch_tags(fetch_tags);
199 }
200
201 let head_local_tracking_branch = format!("refs/remotes/{remote_name}/HEAD");
204 let head_refspec = gix_refspec::parse(
205 format!("HEAD:{head_local_tracking_branch}").as_str().into(),
206 gix_refspec::parse::Operation::Fetch,
207 )
208 .expect("valid")
209 .to_owned();
210 let pending_pack: remote::fetch::Prepare<'_, '_, _> = {
211 let mut connection = remote.connect(remote::Direction::Fetch).await?;
213 if let Some(f) = self.configure_connection.as_mut() {
214 f(&mut connection).map_err(Error::RemoteConnection)?;
215 }
216 let mut fetch_opts = {
217 let mut opts = self.fetch_options.clone();
218 if !opts.extra_refspecs.contains(&head_refspec) {
219 opts.extra_refspecs.push(head_refspec.clone());
220 }
221 if let Some(ref_name) = &self.ref_name {
222 opts.extra_refspecs.push(
223 gix_refspec::parse(ref_name.as_ref().as_bstr(), gix_refspec::parse::Operation::Fetch)
224 .expect("partial names are valid refspecs")
225 .to_owned(),
226 );
227 }
228 opts
229 };
230 match connection.prepare_fetch(&mut progress, fetch_opts.clone()).await {
231 Ok(prepare) => prepare,
232 Err(remote::fetch::prepare::Error::RefMap(remote::ref_map::Error::InitRefMap(
233 gix_protocol::fetch::refmap::init::Error::MappingValidation(err),
234 ))) if err.issues.len() == 1
235 && fetch_opts.extra_refspecs.contains(&head_refspec)
236 && matches!(
237 err.issues.first(),
238 Some(gix_refspec::match_group::validate::Issue::Conflict {
239 destination_full_ref_name,
240 ..
241 }) if *destination_full_ref_name == head_local_tracking_branch
242 ) =>
243 {
244 let head_refspec_idx = fetch_opts
245 .extra_refspecs
246 .iter()
247 .enumerate()
248 .find_map(|(idx, spec)| (*spec == head_refspec).then_some(idx))
249 .expect("it's contained");
250 let connection = remote.connect(remote::Direction::Fetch).await?;
254 fetch_opts.extra_refspecs.remove(head_refspec_idx);
255 connection.prepare_fetch(&mut progress, fetch_opts).await?
256 }
257 Err(err) => return Err(err.into()),
258 }
259 };
260
261 if let Some(ref_name) = &self.ref_name {
263 util::find_custom_refname(pending_pack.ref_map(), ref_name)?;
264 }
265 if pending_pack.ref_map().object_hash != repo.object_hash() {
266 unimplemented!("configure repository to expect a different object hash as advertised by the server")
267 }
268 let reflog_message = {
269 let mut b = self.url.to_bstring();
270 b.insert_str(0, "clone: from ");
271 b
272 };
273 let outcome = pending_pack
274 .with_write_packed_refs_only(true)
275 .with_reflog_message(RefLogMessage::Override {
276 message: reflog_message.clone(),
277 })
278 .with_shallow(self.shallow.clone())
279 .receive(&mut progress, should_interrupt)
280 .await?;
281
282 util::append_config_to_repo_config(repo, config);
283 util::update_head(
284 repo,
285 &outcome.ref_map,
286 reflog_message.as_ref(),
287 remote_name.as_ref(),
288 self.ref_name.as_ref(),
289 )?;
290
291 Ok((self.repo.take().expect("still present"), outcome))
292 }
293
294 #[cfg(all(feature = "worktree-mutation", feature = "blocking-network-client"))]
296 pub fn fetch_then_checkout<P>(
297 &mut self,
298 progress: P,
299 should_interrupt: &std::sync::atomic::AtomicBool,
300 ) -> Result<(crate::clone::PrepareCheckout, crate::remote::fetch::Outcome), Error>
301 where
302 P: crate::NestedProgress,
303 P::SubProgress: 'static,
304 {
305 let (repo, fetch_outcome) = self.fetch_only(progress, should_interrupt)?;
306 Ok((
307 crate::clone::PrepareCheckout {
308 repo: repo.into(),
309 ref_name: self.ref_name.clone(),
310 },
311 fetch_outcome,
312 ))
313 }
314}
315
316mod util;