167 lines · 6.8 KB · Rust
RawDownload
use crate::clone::PrepareFetch; /// The error returned by [`PrepareFetch::fetch_only()`].#[derive(Debug, thiserror::Error)]#[allow(missing_docs)]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)]    RemoteInit(#[from] crate::remote::init::Error),    #[error("Custom configuration of remote to clone from failed")]    RemoteConfiguration(#[source] Box<dyn std::error::Error + Send + Sync>),    #[error("Custom configuration of connection to use when cloning failed")]    RemoteConnection(#[source] Box<dyn std::error::Error + Send + Sync>),    #[error(transparent)]    RemoteName(#[from] crate::config::remote::symbolic_name::Error),    #[error("Failed to load repo-local git configuration before writing")]    LoadConfig(#[from] gix_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: gix_validate::refname::Error,        head_ref_name: crate::bstr::BString,    },    #[error("Failed to update HEAD with values from remote")]    HeadUpdate(#[from] crate::reference::edit::Error),} /// Modificationimpl 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.    ///    /// ### Note for users of `async`    ///    /// Even though    #[gix_protocol::maybe_async::maybe_async]    pub async fn fetch_only<P>(        &mut self,        mut progress: P,        should_interrupt: &std::sync::atomic::AtomicBool,    ) -> Result<(crate::Repository, crate::remote::fetch::Outcome), Error>    where        P: crate::Progress,        P::SubProgress: 'static,    {        use crate::{bstr::ByteVec, remote, 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_ref() {            Some(name) => name.to_owned(),            None => repo                .config                .resolved                .string("clone", None, crate::config::tree::Clone::DEFAULT_REMOTE_NAME.name)                .map(|n| crate::config::tree::Clone::DEFAULT_REMOTE_NAME.try_into_symbolic_name(n))                .transpose()?                .unwrap_or_else(|| "origin".into()),        };         let mut remote = repo            .remote_at(self.url.clone())?            .with_refspecs(                Some(format!("+refs/heads/*:refs/remotes/{remote_name}/*").as_str()),                remote::Direction::Fetch,            )            .expect("valid static spec");        let mut clone_fetch_tags = None;        if let Some(f) = self.configure_remote.as_mut() {            remote = f(remote).map_err(|err| Error::RemoteConfiguration(err))?;        } else {            clone_fetch_tags = remote::fetch::Tags::All.into();        }         let config = util::write_remote_to_local_config_file(&mut remote, remote_name.clone())?;         // Now we are free to apply remote configuration we don't want to be written to disk.        if let Some(fetch_tags) = clone_fetch_tags {            remote = remote.with_fetch_tags(fetch_tags);        }         // 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.        let head_refspec = gix_refspec::parse(            format!("HEAD:refs/remotes/{remote_name}/HEAD").as_str().into(),            gix_refspec::parse::Operation::Fetch,        )        .expect("valid")        .to_owned();        let pending_pack: remote::fetch::Prepare<'_, '_, _> = {            let mut connection = remote.connect(remote::Direction::Fetch).await?;            if let Some(f) = self.configure_connection.as_mut() {                f(&mut connection).map_err(|err| Error::RemoteConnection(err))?;            }            connection                .prepare_fetch(&mut progress, {                    let mut opts = self.fetch_options.clone();                    if !opts.extra_refspecs.contains(&head_refspec) {                        opts.extra_refspecs.push(head_refspec)                    }                    opts                })                .await?        };        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(),            })            .with_shallow(self.shallow.clone())            .receive(progress, should_interrupt)            .await?;         util::append_config_to_repo_config(repo, config);        util::update_head(            repo,            &outcome.ref_map.remote_refs,            reflog_message.as_ref(),            remote_name.as_ref(),        )?;         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<P>(        &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))    }} mod util;