summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/remote/connection
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix/src/remote/connection')
-rw-r--r--vendor/gix/src/remote/connection/access.rs22
-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
-rw-r--r--vendor/gix/src/remote/connection/mod.rs3
-rw-r--r--vendor/gix/src/remote/connection/ref_map.rs21
7 files changed, 197 insertions, 37 deletions
diff --git a/vendor/gix/src/remote/connection/access.rs b/vendor/gix/src/remote/connection/access.rs
index e4c31c3f5..eba603da0 100644
--- a/vendor/gix/src/remote/connection/access.rs
+++ b/vendor/gix/src/remote/connection/access.rs
@@ -4,7 +4,7 @@ use crate::{
};
/// Builder
-impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> {
+impl<'a, 'repo, T> Connection<'a, 'repo, T> {
/// Set a custom credentials callback to provide credentials if the remotes require authentication.
///
/// Otherwise we will use the git configuration to perform the same task as the `git credential` helper program,
@@ -37,8 +37,26 @@ impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> {
}
}
+/// Mutation
+impl<'a, 'repo, T> Connection<'a, 'repo, T> {
+ /// Like [`with_credentials()`][Self::with_credentials()], but without consuming the connection.
+ pub fn set_credentials(
+ &mut self,
+ helper: impl FnMut(gix_credentials::helper::Action) -> gix_credentials::protocol::Result + 'a,
+ ) -> &mut Self {
+ self.authenticate = Some(Box::new(helper));
+ self
+ }
+
+ /// Like [`with_transport_options()`][Self::with_transport_options()], but without consuming the connection.
+ pub fn set_transport_options(&mut self, config: Box<dyn std::any::Any>) -> &mut Self {
+ self.transport_options = Some(config);
+ self
+ }
+}
+
/// Access
-impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> {
+impl<'a, 'repo, T> Connection<'a, 'repo, T> {
/// A utility to return a function that will use this repository's configuration to obtain credentials, similar to
/// what `git credential` is doing.
///
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));
}
diff --git a/vendor/gix/src/remote/connection/mod.rs b/vendor/gix/src/remote/connection/mod.rs
index 09943ecc4..02a09926a 100644
--- a/vendor/gix/src/remote/connection/mod.rs
+++ b/vendor/gix/src/remote/connection/mod.rs
@@ -12,12 +12,11 @@ pub type AuthenticateFn<'a> = Box<dyn FnMut(gix_credentials::helper::Action) ->
///
/// It can be used to perform a variety of operations with the remote without worrying about protocol details,
/// much like a remote procedure call.
-pub struct Connection<'a, 'repo, T, P> {
+pub struct Connection<'a, 'repo, T> {
pub(crate) remote: &'a Remote<'repo>,
pub(crate) authenticate: Option<AuthenticateFn<'a>>,
pub(crate) transport_options: Option<Box<dyn std::any::Any>>,
pub(crate) transport: T,
- pub(crate) progress: P,
}
mod access;
diff --git a/vendor/gix/src/remote/connection/ref_map.rs b/vendor/gix/src/remote/connection/ref_map.rs
index 0206e9002..abf9c8e00 100644
--- a/vendor/gix/src/remote/connection/ref_map.rs
+++ b/vendor/gix/src/remote/connection/ref_map.rs
@@ -71,10 +71,9 @@ impl Default for Options {
}
}
-impl<'remote, 'repo, T, P> Connection<'remote, 'repo, T, P>
+impl<'remote, 'repo, T> Connection<'remote, 'repo, T>
where
T: Transport,
- P: Progress,
{
/// List all references on the remote that have been filtered through our remote's [`refspecs`][crate::Remote::refspecs()]
/// for _fetching_.
@@ -94,8 +93,8 @@ where
/// - `gitoxide.userAgent` is read to obtain the application user agent for git servers and for HTTP servers as well.
#[allow(clippy::result_large_err)]
#[gix_protocol::maybe_async::maybe_async]
- pub async fn ref_map(mut self, options: Options) -> Result<fetch::RefMap, Error> {
- let res = self.ref_map_inner(options).await;
+ pub async fn ref_map(mut self, progress: impl Progress, options: Options) -> Result<fetch::RefMap, Error> {
+ let res = self.ref_map_inner(progress, options).await;
gix_protocol::indicate_end_of_interaction(&mut self.transport)
.await
.ok();
@@ -106,6 +105,7 @@ where
#[gix_protocol::maybe_async::maybe_async]
pub(crate) async fn ref_map_inner(
&mut self,
+ progress: impl Progress,
Options {
prefix_from_spec_as_filter_on_remote,
handshake_parameters,
@@ -125,7 +125,12 @@ where
s
};
let remote = self
- .fetch_refs(prefix_from_spec_as_filter_on_remote, handshake_parameters, &specs)
+ .fetch_refs(
+ prefix_from_spec_as_filter_on_remote,
+ handshake_parameters,
+ &specs,
+ progress,
+ )
.await?;
let num_explicit_specs = self.remote.fetch_specs.len();
let group = gix_refspec::MatchGroup::from_fetch_specs(specs.iter().map(|s| s.to_ref()));
@@ -179,6 +184,7 @@ where
filter_by_prefix: bool,
extra_parameters: Vec<(String, Option<String>)>,
refspecs: &[gix_refspec::RefSpec],
+ mut progress: impl Progress,
) -> Result<HandshakeWithRefs, Error> {
let mut credentials_storage;
let url = self.transport.to_url();
@@ -209,8 +215,7 @@ where
self.transport.configure(&**config)?;
}
let mut outcome =
- gix_protocol::fetch::handshake(&mut self.transport, authenticate, extra_parameters, &mut self.progress)
- .await?;
+ gix_protocol::fetch::handshake(&mut self.transport, authenticate, extra_parameters, &mut progress).await?;
let refs = match outcome.refs.take() {
Some(refs) => refs,
None => {
@@ -236,7 +241,7 @@ where
}
Ok(gix_protocol::ls_refs::Action::Continue)
},
- &mut self.progress,
+ &mut progress,
)
.await?
}