diff options
Diffstat (limited to 'vendor/gix/src/remote/connection/fetch')
-rw-r--r-- | vendor/gix/src/remote/connection/fetch/error.rs | 15 | ||||
-rw-r--r-- | vendor/gix/src/remote/connection/fetch/mod.rs | 28 | ||||
-rw-r--r-- | vendor/gix/src/remote/connection/fetch/negotiate.rs | 15 | ||||
-rw-r--r-- | vendor/gix/src/remote/connection/fetch/receive_pack.rs | 130 |
4 files changed, 163 insertions, 25 deletions
diff --git a/vendor/gix/src/remote/connection/fetch/error.rs b/vendor/gix/src/remote/connection/fetch/error.rs index 0e6a4b840..afcacca13 100644 --- a/vendor/gix/src/remote/connection/fetch/error.rs +++ b/vendor/gix/src/remote/connection/fetch/error.rs @@ -28,6 +28,21 @@ pub enum Error { path: std::path::PathBuf, source: std::io::Error, }, + #[error(transparent)] + ShallowOpen(#[from] crate::shallow::open::Error), + #[error("Server lack feature {feature:?}: {description}")] + MissingServerFeature { + feature: &'static str, + description: &'static str, + }, + #[error("Could not write 'shallow' file to incorporate remote updates after fetching")] + WriteShallowFile(#[from] crate::shallow::write::Error), + #[error("'shallow' file could not be locked in preparation for writing changes")] + LockShallowFile(#[from] gix_lock::acquire::Error), + #[error("Could not obtain configuration to learn if shallow remotes should be rejected")] + RejectShallowRemoteConfig(#[from] config::boolean::Error), + #[error("Receiving objects from shallow remotes is prohibited due to the value of `clone.rejectShallow`")] + RejectShallowRemote, } impl gix_protocol::transport::IsSpuriousError for Error { diff --git a/vendor/gix/src/remote/connection/fetch/mod.rs b/vendor/gix/src/remote/connection/fetch/mod.rs index 4ce631b1e..a51ae7c54 100644 --- a/vendor/gix/src/remote/connection/fetch/mod.rs +++ b/vendor/gix/src/remote/connection/fetch/mod.rs @@ -116,10 +116,9 @@ pub mod prepare { } } -impl<'remote, 'repo, T, P> Connection<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Connection<'remote, 'repo, T> where T: Transport, - P: Progress, { /// Perform a handshake with the remote and obtain a ref-map with `options`, and from there one /// Note that at this point, the `transport` should already be configured using the [`transport_mut()`][Self::transport_mut()] @@ -137,23 +136,25 @@ where #[gix_protocol::maybe_async::maybe_async] pub async fn prepare_fetch( mut self, + progress: impl Progress, options: ref_map::Options, - ) -> Result<Prepare<'remote, 'repo, T, P>, prepare::Error> { + ) -> Result<Prepare<'remote, 'repo, T>, prepare::Error> { if self.remote.refspecs(remote::Direction::Fetch).is_empty() { return Err(prepare::Error::MissingRefSpecs); } - let ref_map = self.ref_map_inner(options).await?; + let ref_map = self.ref_map_inner(progress, options).await?; Ok(Prepare { con: Some(self), ref_map, dry_run: DryRun::No, reflog_message: None, write_packed_refs: WritePackedRefs::Never, + shallow: Default::default(), }) } } -impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Prepare<'remote, 'repo, T> where T: Transport, { @@ -170,19 +171,20 @@ mod receive_pack; pub mod refs; /// A structure to hold the result of the handshake with the remote and configure the upcoming fetch operation. -pub struct Prepare<'remote, 'repo, T, P> +pub struct Prepare<'remote, 'repo, T> where T: Transport, { - con: Option<Connection<'remote, 'repo, T, P>>, + con: Option<Connection<'remote, 'repo, T>>, ref_map: RefMap, dry_run: DryRun, reflog_message: Option<RefLogMessage>, write_packed_refs: WritePackedRefs, + shallow: remote::fetch::Shallow, } /// Builder -impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Prepare<'remote, 'repo, T> where T: Transport, { @@ -212,9 +214,17 @@ where self.reflog_message = reflog_message.into(); self } + + /// Define what to do when the current repository is a shallow clone. + /// + /// *Has no effect if the current repository is not as shallow clone.* + pub fn with_shallow(mut self, shallow: remote::fetch::Shallow) -> Self { + self.shallow = shallow; + self + } } -impl<'remote, 'repo, T, P> Drop for Prepare<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Drop for Prepare<'remote, 'repo, T> where T: Transport, { diff --git a/vendor/gix/src/remote/connection/fetch/negotiate.rs b/vendor/gix/src/remote/connection/fetch/negotiate.rs index f5051ec72..771c5acba 100644 --- a/vendor/gix/src/remote/connection/fetch/negotiate.rs +++ b/vendor/gix/src/remote/connection/fetch/negotiate.rs @@ -1,3 +1,5 @@ +use crate::remote::fetch; + /// The way the negotiation is performed #[derive(Copy, Clone)] pub(crate) enum Algorithm { @@ -16,6 +18,7 @@ pub enum Error { /// Negotiate one round with `algo` by looking at `ref_map` and adjust `arguments` to contain the haves and wants. /// If this is not the first round, the `previous_response` is set with the last recorded server response. /// Returns `true` if the negotiation is done from our side so the server won't keep asking. +#[allow(clippy::too_many_arguments)] pub(crate) fn one_round( algo: Algorithm, round: usize, @@ -24,13 +27,20 @@ pub(crate) fn one_round( fetch_tags: crate::remote::fetch::Tags, arguments: &mut gix_protocol::fetch::Arguments, _previous_response: Option<&gix_protocol::fetch::Response>, + shallow: Option<&fetch::Shallow>, ) -> Result<bool, Error> { let tag_refspec_to_ignore = fetch_tags .to_refspec() .filter(|_| matches!(fetch_tags, crate::remote::fetch::Tags::Included)); + if let Some(fetch::Shallow::Deepen(0)) = shallow { + // Avoid deepening (relative) with zero as it seems to upset the server. Git also doesn't actually + // perform the negotiation for some reason (couldn't find it in code). + return Ok(true); + } + match algo { Algorithm::Naive => { - assert_eq!(round, 1, "Naive always finishes after the first round, and claims."); + assert_eq!(round, 1, "Naive always finishes after the first round, it claims."); let mut has_missing_tracking_branch = false; for mapping in &ref_map.mappings { if tag_refspec_to_ignore.map_or(false, |tag_spec| { @@ -65,10 +75,11 @@ pub(crate) fn one_round( } } - if has_missing_tracking_branch { + if has_missing_tracking_branch || (shallow.is_some() && arguments.is_empty()) { if let Ok(Some(r)) = repo.head_ref() { if let Some(id) = r.target().try_id() { arguments.have(id); + arguments.want(id); } } } diff --git a/vendor/gix/src/remote/connection/fetch/receive_pack.rs b/vendor/gix/src/remote/connection/fetch/receive_pack.rs index 686de5999..99560fbca 100644 --- a/vendor/gix/src/remote/connection/fetch/receive_pack.rs +++ b/vendor/gix/src/remote/connection/fetch/receive_pack.rs @@ -1,23 +1,25 @@ -use std::sync::atomic::AtomicBool; +use std::sync::atomic::{AtomicBool, Ordering}; use gix_odb::FindExt; -use gix_protocol::transport::client::Transport; +use gix_protocol::{ + fetch::Arguments, + transport::{client::Transport, packetline::read::ProgressAction}, +}; use crate::{ + config::tree::Clone, remote, remote::{ connection::fetch::config, fetch, - fetch::{negotiate, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Status}, + fetch::{negotiate, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Shallow, Status}, }, - Progress, + Progress, Repository, }; -impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Prepare<'remote, 'repo, T> where T: Transport, - P: Progress, - P::SubProgress: 'static, { /// Receive the pack and perform the operation as configured by git via `gix-config` or overridden by various builder methods. /// Return `Ok(None)` if there was nothing to do because all remote refs are at the same state as they are locally, or `Ok(Some(outcome))` @@ -62,14 +64,18 @@ where /// - `gitoxide.userAgent` is read to obtain the application user agent for git servers and for HTTP servers as well. /// #[gix_protocol::maybe_async::maybe_async] - pub async fn receive(mut self, should_interrupt: &AtomicBool) -> Result<Outcome, Error> { + pub async fn receive<P>(mut self, mut progress: P, should_interrupt: &AtomicBool) -> Result<Outcome, Error> + where + P: Progress, + P::SubProgress: 'static, + { let mut con = self.con.take().expect("receive() can only be called once"); let handshake = &self.ref_map.handshake; let protocol_version = handshake.server_protocol_version; let fetch = gix_protocol::Command::Fetch; - let progress = &mut con.progress; + let progress = &mut progress; let repo = con.remote.repo; let fetch_features = { let mut f = fetch.default_features(protocol_version, &handshake.capabilities); @@ -82,10 +88,17 @@ where let mut arguments = gix_protocol::fetch::Arguments::new(protocol_version, fetch_features); if matches!(con.remote.fetch_tags, crate::remote::fetch::Tags::Included) { if !arguments.can_use_include_tag() { - unimplemented!("we expect servers to support 'include-tag', otherwise we have to implement another pass to fetch attached tags separately"); + return Err(Error::MissingServerFeature { + feature: "include-tag", + description: + // NOTE: if this is an issue, we could probably do what's proposed here. + "To make this work we would have to implement another pass to fetch attached tags separately", + }); } arguments.use_include_tag(); } + let (shallow_commits, mut shallow_lock) = add_shallow_args(&mut arguments, &self.shallow, repo)?; + let mut previous_response = None::<gix_protocol::fetch::Response>; let mut round = 1; @@ -108,6 +121,7 @@ where con.remote.fetch_tags, &mut arguments, previous_response.as_ref(), + (self.shallow != Shallow::NoChange).then_some(&self.shallow), ) { Ok(_) if arguments.is_empty() => { gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); @@ -137,20 +151,35 @@ where round += 1; let mut reader = arguments.send(&mut con.transport, is_done).await?; if sideband_all { - setup_remote_progress(progress, &mut reader); + setup_remote_progress(progress, &mut reader, should_interrupt); } let response = gix_protocol::fetch::Response::from_line_reader(protocol_version, &mut reader).await?; if response.has_pack() { progress.step(); progress.set_name("receiving pack"); if !sideband_all { - setup_remote_progress(progress, &mut reader); + setup_remote_progress(progress, &mut reader, should_interrupt); } + previous_response = Some(response); break 'negotiation reader; } else { previous_response = Some(response); } }; + let previous_response = previous_response.expect("knowledge of a pack means a response was received"); + if !previous_response.shallow_updates().is_empty() && shallow_lock.is_none() { + let reject_shallow_remote = repo + .config + .resolved + .boolean_filter_by_key("clone.rejectShallow", &mut repo.filter_config_section()) + .map(|val| Clone::REJECT_SHALLOW.enrich_error(val)) + .transpose()? + .unwrap_or(false); + if reject_shallow_remote { + return Err(Error::RejectShallowRemote); + } + shallow_lock = acquire_shallow_lock(repo).map(Some)?; + } let options = gix_pack::bundle::write::Options { thread_limit: config::index_threads(repo)?, @@ -170,7 +199,7 @@ where reader }, Some(repo.objects.store_ref().path().join("pack")), - con.progress, + progress, should_interrupt, Some(Box::new({ let repo = repo.clone(); @@ -187,6 +216,12 @@ where gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); } + if let Some(shallow_lock) = shallow_lock { + if !previous_response.shallow_updates().is_empty() { + crate::shallow::write(shallow_lock, shallow_commits, previous_response.shallow_updates())?; + } + } + let update_refs = refs::update( repo, self.reflog_message @@ -221,9 +256,61 @@ where } } +fn acquire_shallow_lock(repo: &Repository) -> Result<gix_lock::File, Error> { + gix_lock::File::acquire_to_update_resource(repo.shallow_file(), gix_lock::acquire::Fail::Immediately, None) + .map_err(Into::into) +} + +fn add_shallow_args( + args: &mut Arguments, + shallow: &Shallow, + repo: &Repository, +) -> Result<(Option<crate::shallow::Commits>, Option<gix_lock::File>), Error> { + let expect_change = *shallow != Shallow::NoChange; + let shallow_lock = expect_change.then(|| acquire_shallow_lock(repo)).transpose()?; + + let shallow_commits = repo.shallow_commits()?; + if (shallow_commits.is_some() || expect_change) && !args.can_use_shallow() { + // NOTE: if this is an issue, we can always unshallow the repo ourselves. + return Err(Error::MissingServerFeature { + feature: "shallow", + description: "shallow clones need server support to remain shallow, otherwise bigger than expected packs are sent effectively unshallowing the repository", + }); + } + if let Some(shallow_commits) = &shallow_commits { + for commit in shallow_commits.iter() { + args.shallow(commit); + } + } + match shallow { + Shallow::NoChange => {} + Shallow::DepthAtRemote(commits) => args.deepen(commits.get() as usize), + Shallow::Deepen(commits) => { + args.deepen(*commits as usize); + args.deepen_relative(); + } + Shallow::Since { cutoff } => { + args.deepen_since(cutoff.seconds_since_unix_epoch as usize); + } + Shallow::Exclude { + remote_refs, + since_cutoff, + } => { + if let Some(cutoff) = since_cutoff { + args.deepen_since(cutoff.seconds_since_unix_epoch as usize); + } + for ref_ in remote_refs { + args.deepen_not(ref_.as_ref().as_bstr()); + } + } + } + Ok((shallow_commits, shallow_lock)) +} + fn setup_remote_progress<P>( progress: &mut P, reader: &mut Box<dyn gix_protocol::transport::client::ExtendedBufRead + Unpin + '_>, + should_interrupt: &AtomicBool, ) where P: Progress, P::SubProgress: 'static, @@ -231,8 +318,23 @@ fn setup_remote_progress<P>( use gix_protocol::transport::client::ExtendedBufRead; reader.set_progress_handler(Some(Box::new({ let mut remote_progress = progress.add_child_with_id("remote", ProgressId::RemoteProgress.into()); + // SAFETY: Ugh, so, with current Rust I can't declare lifetimes in the involved traits the way they need to + // be and I also can't use scoped threads to pump from local scopes to an Arc version that could be + // used here due to the this being called from sync AND async code (and the async version doesn't work + // with a surrounding `std::thread::scope()`. + // Thus there is only claiming this is 'static which we know works for *our* implementations of ExtendedBufRead + // and typical implementations, but of course it's possible for user code to come along and actually move this + // handler into a context where it can outlive the current function. Is this going to happen? Probably not unless + // somebody really wants to break it. So, with standard usage this value is never used past its actual lifetime. + #[allow(unsafe_code)] + let should_interrupt: &'static AtomicBool = unsafe { std::mem::transmute(should_interrupt) }; move |is_err: bool, data: &[u8]| { - gix_protocol::RemoteProgress::translate_to_progress(is_err, data, &mut remote_progress) + gix_protocol::RemoteProgress::translate_to_progress(is_err, data, &mut remote_progress); + if should_interrupt.load(Ordering::Relaxed) { + ProgressAction::Interrupt + } else { + ProgressAction::Continue + } } }) as gix_protocol::transport::client::HandleProgress)); } |