use crate::{clone::PrepareFetch, Repository}; /// The error returned by [`PrepareFetch::fetch_only()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] #[cfg(feature = "blocking-network-client")] pub enum Error { #[error(transparent)] Connect(#[from] crate::remote::connect::Error), #[error(transparent)] PrepareFetch(#[from] crate::remote::fetch::prepare::Error), #[error(transparent)] Fetch(#[from] crate::remote::fetch::Error), #[error(transparent)] RemoteConfiguration(#[from] crate::remote::init::Error), #[error("Default remote configured at `clone.defaultRemoteName` is invalid")] RemoteName(#[from] crate::remote::name::Error), #[error("Failed to load repo-local git configuration before writing")] LoadConfig(#[from] git_config::file::init::from_paths::Error), #[error("Failed to store configured remote in memory")] SaveConfig(#[from] crate::remote::save::AsError), #[error("Failed to write repository configuration to disk")] SaveConfigIo(#[from] std::io::Error), #[error("The remote HEAD points to a reference named {head_ref_name:?} which is invalid.")] InvalidHeadRef { source: git_validate::refname::Error, head_ref_name: crate::bstr::BString, }, #[error("Failed to update HEAD with values from remote")] HeadUpdate(#[from] crate::reference::edit::Error), } /// Modification impl PrepareFetch { /// Fetch a pack and update local branches according to refspecs, providing `progress` and checking `should_interrupt` to stop /// the operation. /// On success, the persisted repository is returned, and this method must not be called again to avoid a **panic**. /// On error, the method may be called again to retry as often as needed. /// /// If the remote repository was empty, that is newly initialized, the returned repository will also be empty and like /// it was newly initialized. /// /// Note that all data we created will be removed once this instance drops if the operation wasn't successful. #[cfg(feature = "blocking-network-client")] pub fn fetch_only
( &mut self, progress: P, should_interrupt: &std::sync::atomic::AtomicBool, ) -> Result<(Repository, crate::remote::fetch::Outcome), Error> where P: crate::Progress, P::SubProgress: 'static, { use crate::{bstr::ByteVec, remote::fetch::RefLogMessage}; let repo = self .repo .as_mut() .expect("user error: multiple calls are allowed only until it succeeds"); let remote_name = match self.remote_name.as_deref() { Some(name) => name.to_owned(), None => repo .config .resolved .string("clone", None, "defaultRemoteName") .map(|n| crate::remote::name::validated(n.to_string())) .unwrap_or_else(|| Ok("origin".into()))?, }; let mut remote = repo .remote_at(self.url.clone())? .with_refspec( format!("+refs/heads/*:refs/remotes/{remote_name}/*").as_str(), crate::remote::Direction::Fetch, ) .expect("valid static spec"); if let Some(f) = self.configure_remote.as_mut() { remote = f(remote)?; } let config = util::write_remote_to_local_config_file(&mut remote, remote_name.clone())?; // Add HEAD after the remote was written to config, we need it to know what to checkout later, and assure // the ref that HEAD points to is present no matter what. remote.fetch_specs.push( git_refspec::parse( format!("HEAD:refs/remotes/{remote_name}/HEAD").as_str().into(), git_refspec::parse::Operation::Fetch, ) .expect("valid") .to_owned(), ); let pending_pack: crate::remote::fetch::Prepare<'_, '_, _, _> = remote .connect(crate::remote::Direction::Fetch, progress)? .prepare_fetch(self.fetch_options.clone())?; if pending_pack.ref_map().object_hash != repo.object_hash() { unimplemented!("configure repository to expect a different object hash as advertised by the server") } let reflog_message = { let mut b = self.url.to_bstring(); b.insert_str(0, "clone: from "); b }; let outcome = pending_pack .with_write_packed_refs_only(true) .with_reflog_message(RefLogMessage::Override { message: reflog_message.clone(), }) .receive(should_interrupt)?; util::replace_changed_local_config_file(repo, config); util::update_head( repo, &outcome.ref_map.remote_refs, reflog_message.as_ref(), &remote_name, )?; Ok((self.repo.take().expect("still present"), outcome)) } /// Similar to [`fetch_only()`][Self::fetch_only()`], but passes ownership to a utility type to configure a checkout operation. #[cfg(feature = "blocking-network-client")] pub fn fetch_then_checkout
(
&mut self,
progress: P,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> Result<(crate::clone::PrepareCheckout, crate::remote::fetch::Outcome), Error>
where
P: crate::Progress,
P::SubProgress: 'static,
{
let (repo, fetch_outcome) = self.fetch_only(progress, should_interrupt)?;
Ok((crate::clone::PrepareCheckout { repo: repo.into() }, fetch_outcome))
}
}
/// Builder
impl PrepareFetch {
/// Set additional options to adjust parts of the fetch operation that are not affected by the git configuration.
#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
pub fn with_fetch_options(mut self, opts: crate::remote::ref_map::Options) -> Self {
self.fetch_options = opts;
self
}
/// Use `f` to apply arbitrary changes to the remote that is about to be used to fetch a pack.
///
/// The passed in `remote` will be un-named and pre-configured to be a default remote as we know it from git-clone.
/// It is not yet present in the configuration of the repository,
/// but each change it will eventually be written to the configuration prior to performing a the fetch operation.
pub fn configure_remote(
mut self,
f: impl FnMut(crate::Remote<'_>) -> Result