summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/clone/fetch/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix/src/clone/fetch/mod.rs')
-rw-r--r--vendor/gix/src/clone/fetch/mod.rs212
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;