summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/remote/connection/fetch
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix/src/remote/connection/fetch')
-rw-r--r--vendor/gix/src/remote/connection/fetch/error.rs15
-rw-r--r--vendor/gix/src/remote/connection/fetch/mod.rs28
-rw-r--r--vendor/gix/src/remote/connection/fetch/negotiate.rs15
-rw-r--r--vendor/gix/src/remote/connection/fetch/receive_pack.rs130
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));
}