diff options
Diffstat (limited to 'vendor/gix/src/clone/fetch/mod.rs')
-rw-r--r-- | vendor/gix/src/clone/fetch/mod.rs | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/vendor/gix/src/clone/fetch/mod.rs b/vendor/gix/src/clone/fetch/mod.rs new file mode 100644 index 000000000..d663b47ea --- /dev/null +++ b/vendor/gix/src/clone/fetch/mod.rs @@ -0,0 +1,212 @@ +use crate::{bstr::BString, 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)] + 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(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: 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<P>( + &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, 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<'_, '_, _, _> = + remote.connect(remote::Direction::Fetch, progress)?.prepare_fetch({ + let mut opts = self.fetch_options.clone(); + if !opts.extra_refspecs.contains(&head_refspec) { + opts.extra_refspecs.push(head_refspec) + } + opts + })?; + 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::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)) + } +} + +/// 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, + /// _all changes done in `f()` will be persisted_. + /// + /// It can also be used to configure additional options, like those for fetching tags. Note that + /// [with_fetch_tags()][crate::Remote::with_fetch_tags()] should be called here to configure the clone as desired. + /// Otherwise a clone is configured to be complete and fetches all tags, not only those reachable from all branches. + pub fn configure_remote( + mut self, + f: impl FnMut(crate::Remote<'_>) -> Result<crate::Remote<'_>, Box<dyn std::error::Error + Send + Sync>> + 'static, + ) -> Self { + self.configure_remote = Some(Box::new(f)); + self + } + + /// Set the remote's name to the given value after it was configured using the function provided via + /// [`configure_remote()`][Self::configure_remote()]. + /// + /// If not set here, it defaults to `origin` or the value of `clone.defaultRemoteName`. + pub fn with_remote_name(mut self, name: impl Into<BString>) -> Result<Self, crate::remote::name::Error> { + self.remote_name = Some(crate::remote::name::validated(name)?); + Ok(self) + } +} + +/// Consumption +impl PrepareFetch { + /// Persist the contained repository as is even if an error may have occurred when fetching from the remote. + pub fn persist(mut self) -> Repository { + self.repo.take().expect("present and consumed once") + } +} + +impl Drop for PrepareFetch { + fn drop(&mut self) { + if let Some(repo) = self.repo.take() { + std::fs::remove_dir_all(repo.work_dir().unwrap_or_else(|| repo.path())).ok(); + } + } +} + +impl From<PrepareFetch> for Repository { + fn from(prep: PrepareFetch) -> Self { + prep.persist() + } +} + +#[cfg(feature = "blocking-network-client")] +mod util; |