diff options
Diffstat (limited to 'vendor/gix/src')
53 files changed, 886 insertions, 379 deletions
diff --git a/vendor/gix/src/clone/access.rs b/vendor/gix/src/clone/access.rs index 5b6e5fbab..1c817e939 100644 --- a/vendor/gix/src/clone/access.rs +++ b/vendor/gix/src/clone/access.rs @@ -1,6 +1,4 @@ -use crate::bstr::BString; -use crate::clone::PrepareFetch; -use crate::Repository; +use crate::{bstr::BString, clone::PrepareFetch, Repository}; /// Builder impl PrepareFetch { @@ -12,7 +10,7 @@ impl PrepareFetch { /// _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. + /// [`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, diff --git a/vendor/gix/src/clone/fetch/mod.rs b/vendor/gix/src/clone/fetch/mod.rs index 59f820675..e20cc96cb 100644 --- a/vendor/gix/src/clone/fetch/mod.rs +++ b/vendor/gix/src/clone/fetch/mod.rs @@ -44,7 +44,12 @@ impl PrepareFetch { /// it was newly initialized. /// /// Note that all data we created will be removed once this instance drops if the operation wasn't successful. - pub fn fetch_only<P>( + /// + /// ### Note for users of `async` + /// + /// Even though + #[gix_protocol::maybe_async::maybe_async] + pub async fn fetch_only<P>( &mut self, mut progress: P, should_interrupt: &std::sync::atomic::AtomicBool, @@ -101,17 +106,19 @@ impl PrepareFetch { .expect("valid") .to_owned(); let pending_pack: remote::fetch::Prepare<'_, '_, _> = { - let mut connection = remote.connect(remote::Direction::Fetch)?; + let mut connection = remote.connect(remote::Direction::Fetch).await?; if let Some(f) = self.configure_connection.as_mut() { f(&mut connection).map_err(|err| Error::RemoteConnection(err))?; } - connection.prepare_fetch(&mut progress, { - let mut opts = self.fetch_options.clone(); - if !opts.extra_refspecs.contains(&head_refspec) { - opts.extra_refspecs.push(head_refspec) - } - opts - })? + connection + .prepare_fetch(&mut progress, { + let mut opts = self.fetch_options.clone(); + if !opts.extra_refspecs.contains(&head_refspec) { + opts.extra_refspecs.push(head_refspec) + } + opts + }) + .await? }; if pending_pack.ref_map().object_hash != repo.object_hash() { unimplemented!("configure repository to expect a different object hash as advertised by the server") @@ -127,7 +134,8 @@ impl PrepareFetch { message: reflog_message.clone(), }) .with_shallow(self.shallow.clone()) - .receive(progress, should_interrupt)?; + .receive(progress, should_interrupt) + .await?; util::append_config_to_repo_config(repo, config); util::update_head( @@ -141,6 +149,7 @@ impl PrepareFetch { } /// 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, @@ -155,5 +164,4 @@ impl PrepareFetch { } } -#[cfg(feature = "blocking-network-client")] mod util; diff --git a/vendor/gix/src/clone/fetch/util.rs b/vendor/gix/src/clone/fetch/util.rs index ac8943f6e..cb79669ac 100644 --- a/vendor/gix/src/clone/fetch/util.rs +++ b/vendor/gix/src/clone/fetch/util.rs @@ -185,7 +185,7 @@ pub fn update_head( /// Setup the remote configuration for `branch` so that it points to itself, but on the remote, if and only if currently /// saved refspecs are able to match it. -/// For that we reload the remote of `remote_name` and use its ref_specs for match. +/// For that we reload the remote of `remote_name` and use its `ref_specs` for match. fn setup_branch_config( repo: &mut Repository, branch: &FullNameRef, diff --git a/vendor/gix/src/clone/mod.rs b/vendor/gix/src/clone/mod.rs index 43024e0b4..9ec226135 100644 --- a/vendor/gix/src/clone/mod.rs +++ b/vendor/gix/src/clone/mod.rs @@ -160,7 +160,7 @@ mod access_feat { } /// -#[cfg(feature = "blocking-network-client")] +#[cfg(any(feature = "async-network-client-async-std", feature = "blocking-network-client"))] pub mod fetch; /// diff --git a/vendor/gix/src/commit.rs b/vendor/gix/src/commit.rs index a58954a36..68e1eeba7 100644 --- a/vendor/gix/src/commit.rs +++ b/vendor/gix/src/commit.rs @@ -31,7 +31,7 @@ pub mod describe { use crate::{bstr::BStr, ext::ObjectIdExt, Repository}; - /// The result of [try_resolve()][Platform::try_resolve()]. + /// The result of [`try_resolve()`][Platform::try_resolve()]. pub struct Resolution<'repo> { /// The outcome of the describe operation. pub outcome: gix_revision::describe::Outcome<'static>, @@ -47,12 +47,12 @@ pub mod describe { } } - /// The error returned by [try_format()][Platform::try_format()]. + /// The error returned by [`try_format()`][Platform::try_format()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] - Describe(#[from] gix_revision::describe::Error<gix_odb::store::find::Error>), + Describe(#[from] gix_revision::describe::Error), #[error("Could not produce an unambiguous shortened id for formatting.")] ShortId(#[from] crate::id::shorten::Error), #[error(transparent)] @@ -201,15 +201,18 @@ pub mod describe { /// to save ~40% of time. pub fn try_resolve(&self) -> Result<Option<Resolution<'repo>>, Error> { // TODO: dirty suffix with respective dirty-detection - let outcome = gix_revision::describe( - &self.id, + let mut graph = gix_revision::Graph::new( |id, buf| { - Ok(self - .repo + self.repo .objects - .try_find(id, buf)? - .and_then(|d| d.try_into_commit_iter())) + .try_find(id, buf) + .map(|r| r.and_then(|d| d.try_into_commit_iter())) }, + gix_commitgraph::Graph::from_info_dir(self.repo.objects.store_ref().path().join("info")).ok(), + ); + let outcome = gix_revision::describe( + &self.id, + &mut graph, gix_revision::describe::Options { name_by_oid: self.select.names(self.repo)?, fallback_to_oid: self.id_as_fallback, @@ -218,7 +221,7 @@ pub mod describe { }, )?; - Ok(outcome.map(|outcome| crate::commit::describe::Resolution { + Ok(outcome.map(|outcome| Resolution { outcome, id: self.id.attach(self.repo), })) diff --git a/vendor/gix/src/config/cache/access.rs b/vendor/gix/src/config/cache/access.rs index 77324efe3..cea56f973 100644 --- a/vendor/gix/src/config/cache/access.rs +++ b/vendor/gix/src/config/cache/access.rs @@ -47,8 +47,7 @@ impl Cache { .get_or_init(|| { self.resolved .string_by_key(Gitoxide::USER_AGENT.logical_name().as_str()) - .map(|s| s.to_string()) - .unwrap_or_else(|| crate::env::agent().into()) + .map_or_else(|| crate::env::agent().into(), |s| s.to_string()) }) .to_owned(); ("agent", Some(gix_protocol::agent(agent).into())) diff --git a/vendor/gix/src/config/cache/init.rs b/vendor/gix/src/config/cache/init.rs index ee20e0354..6fcbcc4ec 100644 --- a/vendor/gix/src/config/cache/init.rs +++ b/vendor/gix/src/config/cache/init.rs @@ -151,7 +151,7 @@ impl Cache { lenient_config, )?; let object_kind_hint = util::disambiguate_hint(&config, lenient_config)?; - let (pack_cache_bytes, object_cache_bytes) = + let (static_pack_cache_limit_bytes, pack_cache_bytes, object_cache_bytes) = util::parse_object_caches(&config, lenient_config, filter_config_section)?; // NOTE: When adding a new initial cache, consider adjusting `reread_values_and_clear_caches()` as well. Ok(Cache { @@ -159,6 +159,7 @@ impl Cache { use_multi_pack_index, object_hash, object_kind_hint, + static_pack_cache_limit_bytes, pack_cache_bytes, object_cache_bytes, reflog, @@ -222,8 +223,11 @@ impl Cache { self.url_rewrite = Default::default(); self.diff_renames = Default::default(); self.diff_algorithm = Default::default(); - (self.pack_cache_bytes, self.object_cache_bytes) = - util::parse_object_caches(config, self.lenient_config, self.filter_config_section)?; + ( + self.static_pack_cache_limit_bytes, + self.pack_cache_bytes, + self.object_cache_bytes, + ) = util::parse_object_caches(config, self.lenient_config, self.filter_config_section)?; #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] { self.url_scheme = Default::default(); @@ -423,10 +427,6 @@ fn apply_environment_overrides( objects, &[ { - let key = &gitoxide::Objects::NO_REPLACE; - (env(key), key.name) - }, - { let key = &gitoxide::Objects::REPLACE_REF_BASE; (env(key), key.name) }, @@ -487,6 +487,10 @@ fn apply_environment_overrides( let key = &Core::SSH_COMMAND; (env(key), key.name, git_prefix) }, + { + let key = &Core::USE_REPLACE_REFS; + (env(key), key.name, objects) + }, ] { if let Some(value) = var_as_bstring(var, permission) { section.push_with_comment( diff --git a/vendor/gix/src/config/cache/util.rs b/vendor/gix/src/config/cache/util.rs index d5a0a4acb..7c478fcf9 100644 --- a/vendor/gix/src/config/cache/util.rs +++ b/vendor/gix/src/config/cache/util.rs @@ -40,8 +40,7 @@ pub(crate) fn config_bool( ); config .boolean_by_key(key_str) - .map(|res| key.enrich_error(res)) - .unwrap_or(Ok(default)) + .map_or(Ok(default), |res| key.enrich_error(res)) .map_err(Error::from) .with_lenient_default(lenient) } @@ -73,7 +72,12 @@ pub(crate) fn parse_object_caches( config: &gix_config::File<'static>, lenient: bool, mut filter_config_section: fn(&gix_config::file::Metadata) -> bool, -) -> Result<(Option<usize>, usize), Error> { +) -> Result<(Option<usize>, Option<usize>, usize), Error> { + let static_pack_cache_limit = config + .integer_filter_by_key("core.deltaBaseCacheLimit", &mut filter_config_section) + .map(|res| gitoxide::Core::DEFAULT_PACK_CACHE_MEMORY_LIMIT.try_into_usize(res)) + .transpose() + .with_leniency(lenient)?; let pack_cache_bytes = config .integer_filter_by_key("core.deltaBaseCacheLimit", &mut filter_config_section) .map(|res| Core::DELTA_BASE_CACHE_LIMIT.try_into_usize(res)) @@ -85,7 +89,7 @@ pub(crate) fn parse_object_caches( .transpose() .with_leniency(lenient)? .unwrap_or_default(); - Ok((pack_cache_bytes, object_cache_bytes)) + Ok((static_pack_cache_limit, pack_cache_bytes, object_cache_bytes)) } pub(crate) fn parse_core_abbrev( diff --git a/vendor/gix/src/config/mod.rs b/vendor/gix/src/config/mod.rs index 5da569605..3353806f8 100644 --- a/vendor/gix/src/config/mod.rs +++ b/vendor/gix/src/config/mod.rs @@ -438,7 +438,7 @@ pub mod transport { /// Utility type to keep pre-obtained configuration values, only for those required during initial setup /// and other basic operations that are common enough to warrant a permanent cache. /// -/// All other values are obtained lazily using OnceCell. +/// All other values are obtained lazily using `OnceCell`. #[derive(Clone)] pub(crate) struct Cache { pub resolved: crate::Config, @@ -470,6 +470,8 @@ pub(crate) struct Cache { pub(crate) pack_cache_bytes: Option<usize>, /// The amount of bytes to use for caching whole objects, or 0 to turn it off entirely. pub(crate) object_cache_bytes: usize, + /// The amount of bytes we can hold in our static LRU cache. Otherwise, go with the defaults. + pub(crate) static_pack_cache_limit_bytes: Option<usize>, /// The config section filter from the options used to initialize this instance. Keep these in sync! filter_config_section: fn(&gix_config::file::Metadata) -> bool, /// The object kind to pick if a prefix is ambiguous. diff --git a/vendor/gix/src/config/overrides.rs b/vendor/gix/src/config/overrides.rs index f43e8471b..4bdf4a13f 100644 --- a/vendor/gix/src/config/overrides.rs +++ b/vendor/gix/src/config/overrides.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use crate::bstr::{BStr, BString, ByteSlice}; -/// The error returned by [SnapshotMut::apply_cli_overrides()][crate::config::SnapshotMut::append_config()]. +/// The error returned by [`SnapshotMut::apply_cli_overrides()`][crate::config::SnapshotMut::append_config()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/vendor/gix/src/config/snapshot/credential_helpers.rs b/vendor/gix/src/config/snapshot/credential_helpers.rs index 5a07e9fe2..c4eef35d6 100644 --- a/vendor/gix/src/config/snapshot/credential_helpers.rs +++ b/vendor/gix/src/config/snapshot/credential_helpers.rs @@ -13,7 +13,7 @@ use crate::{ mod error { use crate::bstr::BString; - /// The error returned by [Snapshot::credential_helpers()][super::Snapshot::credential_helpers()]. + /// The error returned by [`Snapshot::credential_helpers()`][super::Snapshot::credential_helpers()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/vendor/gix/src/config/tree/keys.rs b/vendor/gix/src/config/tree/keys.rs index 1cdd187d0..b03fa49c6 100644 --- a/vendor/gix/src/config/tree/keys.rs +++ b/vendor/gix/src/config/tree/keys.rs @@ -179,10 +179,10 @@ pub type Url = Any<validate::Url>; /// A key that represents a UTF-8 string. pub type String = Any<validate::String>; -/// A key that represents a RefSpec for pushing. +/// A key that represents a `RefSpec` for pushing. pub type PushRefSpec = Any<validate::PushRefSpec>; -/// A key that represents a RefSpec for fetching. +/// A key that represents a `RefSpec` for fetching. pub type FetchRefSpec = Any<validate::FetchRefSpec>; mod duration { @@ -511,7 +511,8 @@ pub mod validate { gix_config::Integer::try_from(value)? .to_decimal() .ok_or_else(|| format!("integer {value} cannot be represented as `usize`"))?, - )?; + ) + .map_err(|_| "cannot use sign for unsigned integer")?; Ok(()) } } diff --git a/vendor/gix/src/config/tree/mod.rs b/vendor/gix/src/config/tree/mod.rs index b378b8c49..3f69ccb97 100644 --- a/vendor/gix/src/config/tree/mod.rs +++ b/vendor/gix/src/config/tree/mod.rs @@ -34,6 +34,8 @@ pub(crate) mod root { pub const DIFF: sections::Diff = sections::Diff; /// The `extensions` section. pub const EXTENSIONS: sections::Extensions = sections::Extensions; + /// The `fetch` section. + pub const FETCH: sections::Fetch = sections::Fetch; /// The `gitoxide` section. pub const GITOXIDE: sections::Gitoxide = sections::Gitoxide; /// The `http` section. @@ -69,6 +71,7 @@ pub(crate) mod root { &Self::CREDENTIAL, &Self::DIFF, &Self::EXTENSIONS, + &Self::FETCH, &Self::GITOXIDE, &Self::HTTP, &Self::INDEX, @@ -87,9 +90,9 @@ pub(crate) mod root { mod sections; pub use sections::{ - branch, checkout, core, credential, diff, extensions, gitoxide, http, index, protocol, remote, ssh, Author, Branch, - Checkout, Clone, Committer, Core, Credential, Diff, Extensions, Gitoxide, Http, Index, Init, Pack, Protocol, - Remote, Safe, Ssh, Url, User, + branch, checkout, core, credential, diff, extensions, fetch, gitoxide, http, index, protocol, remote, ssh, Author, + Branch, Checkout, Clone, Committer, Core, Credential, Diff, Extensions, Fetch, Gitoxide, Http, Index, Init, Pack, + Protocol, Remote, Safe, Ssh, Url, User, }; /// Generic value implementations for static instantiation. @@ -99,7 +102,7 @@ pub mod keys; pub mod key { /// pub mod validate { - /// The error returned by [Key::validate()][crate::config::tree::Key::validate()]. + /// The error returned by [`Key::validate()`][crate::config::tree::Key::validate()]. #[derive(Debug, thiserror::Error)] #[error(transparent)] #[allow(missing_docs)] @@ -110,7 +113,7 @@ pub mod key { } /// pub mod validate_assignment { - /// The error returned by [Key::validated_assignment*()][crate::config::tree::Key::validated_assignment_fmt()]. + /// The error returned by [`Key::validated_assignment`*()][crate::config::tree::Key::validated_assignment_fmt()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/vendor/gix/src/config/tree/sections/core.rs b/vendor/gix/src/config/tree/sections/core.rs index 6ea0580e1..93d2fcd01 100644 --- a/vendor/gix/src/config/tree/sections/core.rs +++ b/vendor/gix/src/config/tree/sections/core.rs @@ -36,7 +36,7 @@ impl Core { LogAllRefUpdates::new_with_validate("logAllRefUpdates", &config::Tree::CORE, validate::LogAllRefUpdates); /// The `core.precomposeUnicode` key. /// - /// Needs application to use [env::args_os][crate::env::args_os()] to conform all input paths before they are used. + /// Needs application to use [`env::args_os`][crate::env::args_os()] to conform all input paths before they are used. pub const PRECOMPOSE_UNICODE: keys::Boolean = keys::Boolean::new_boolean("precomposeUnicode", &config::Tree::CORE) .with_note("application needs to conform all program input by using gix::env::args_os()"); /// The `core.repositoryFormatVersion` key. @@ -63,6 +63,9 @@ impl Core { /// The `core.sshCommand` key. pub const SSH_COMMAND: keys::Executable = keys::Executable::new_executable("sshCommand", &config::Tree::CORE) .with_environment_override("GIT_SSH_COMMAND"); + /// The `core.useReplaceRefs` key. + pub const USE_REPLACE_REFS: keys::Boolean = keys::Boolean::new_boolean("useReplaceRefs", &config::Tree::CORE) + .with_environment_override("GIT_NO_REPLACE_OBJECTS"); } impl Section for Core { @@ -92,6 +95,7 @@ impl Section for Core { &Self::EXCLUDES_FILE, &Self::ATTRIBUTES_FILE, &Self::SSH_COMMAND, + &Self::USE_REPLACE_REFS, ] } } diff --git a/vendor/gix/src/config/tree/sections/fetch.rs b/vendor/gix/src/config/tree/sections/fetch.rs new file mode 100644 index 000000000..117cabfd2 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/fetch.rs @@ -0,0 +1,68 @@ +use crate::{ + config, + config::tree::{keys, Fetch, Key, Section}, +}; + +impl Fetch { + /// The `fetch.negotiationAlgorithm` key. + pub const NEGOTIATION_ALGORITHM: NegotiationAlgorithm = NegotiationAlgorithm::new_with_validate( + "negotiationAlgorithm", + &config::Tree::FETCH, + validate::NegotiationAlgorithm, + ); +} + +impl Section for Fetch { + fn name(&self) -> &str { + "fetch" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::NEGOTIATION_ALGORITHM] + } +} + +/// The `fetch.negotiationAlgorithm` key. +pub type NegotiationAlgorithm = keys::Any<validate::NegotiationAlgorithm>; + +mod algorithm { + use std::borrow::Cow; + + use gix_object::bstr::ByteSlice; + + use crate::{ + bstr::BStr, + config::{key::GenericErrorWithValue, tree::sections::fetch::NegotiationAlgorithm}, + remote::fetch::negotiate, + }; + + impl NegotiationAlgorithm { + /// Derive the negotiation algorithm identified by `name`, case-sensitively. + pub fn try_into_negotiation_algorithm( + &'static self, + name: Cow<'_, BStr>, + ) -> Result<negotiate::Algorithm, GenericErrorWithValue> { + Ok(match name.as_ref().as_bytes() { + b"noop" => negotiate::Algorithm::Noop, + b"consecutive" | b"default" => negotiate::Algorithm::Consecutive, + b"skipping" => negotiate::Algorithm::Skipping, + _ => return Err(GenericErrorWithValue::from_value(self, name.into_owned())), + }) + } + } +} + +mod validate { + use crate::{ + bstr::BStr, + config::tree::{keys, Fetch}, + }; + + pub struct NegotiationAlgorithm; + impl keys::Validate for NegotiationAlgorithm { + fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { + Fetch::NEGOTIATION_ALGORITHM.try_into_negotiation_algorithm(value.into())?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/gitoxide.rs b/vendor/gix/src/config/tree/sections/gitoxide.rs index 7d60f1287..f07d494fb 100644 --- a/vendor/gix/src/config/tree/sections/gitoxide.rs +++ b/vendor/gix/src/config/tree/sections/gitoxide.rs @@ -67,6 +67,11 @@ mod subsections { pub struct Core; impl Core { + /// The `gitoxide.core.defaultPackCacheMemoryLimit` key. + pub const DEFAULT_PACK_CACHE_MEMORY_LIMIT: keys::UnsignedInteger = + keys::UnsignedInteger::new_unsigned_integer("defaultPackCacheMemoryLimit", &Gitoxide::CORE).with_note( + "If unset, we default to 96MB memory cap for the default 64 slot LRU cache for object deltas.", + ); /// The `gitoxide.core.useNsec` key. pub const USE_NSEC: keys::Boolean = keys::Boolean::new_boolean("useNsec", &Gitoxide::CORE) .with_note("A runtime version of the USE_NSEC build flag."); @@ -89,7 +94,12 @@ mod subsections { } fn keys(&self) -> &[&dyn Key] { - &[&Self::USE_NSEC, &Self::USE_STDEV, &Self::SHALLOW_FILE] + &[ + &Self::DEFAULT_PACK_CACHE_MEMORY_LIMIT, + &Self::USE_NSEC, + &Self::USE_STDEV, + &Self::SHALLOW_FILE, + ] } fn parent(&self) -> Option<&dyn Section> { @@ -307,8 +317,7 @@ mod subsections { .with_note("If unset or 0, there is no object cache") .with_environment_override("GITOXIDE_OBJECT_CACHE_MEMORY"); /// The `gitoxide.objects.noReplace` key. - pub const NO_REPLACE: keys::Boolean = keys::Boolean::new_boolean("noReplace", &Gitoxide::OBJECTS) - .with_environment_override("GIT_NO_REPLACE_OBJECTS"); + pub const NO_REPLACE: keys::Boolean = keys::Boolean::new_boolean("noReplace", &Gitoxide::OBJECTS); /// The `gitoxide.objects.replaceRefBase` key. pub const REPLACE_REF_BASE: keys::Any = keys::Any::new("replaceRefBase", &Gitoxide::OBJECTS).with_environment_override("GIT_REPLACE_REF_BASE"); @@ -320,7 +329,7 @@ mod subsections { } fn keys(&self) -> &[&dyn Key] { - &[&Self::CACHE_LIMIT, &Self::NO_REPLACE, &Self::REPLACE_REF_BASE] + &[&Self::CACHE_LIMIT, &Self::REPLACE_REF_BASE] } fn parent(&self) -> Option<&dyn Section> { diff --git a/vendor/gix/src/config/tree/sections/index.rs b/vendor/gix/src/config/tree/sections/index.rs index d03322247..08f7ec1bd 100644 --- a/vendor/gix/src/config/tree/sections/index.rs +++ b/vendor/gix/src/config/tree/sections/index.rs @@ -13,12 +13,14 @@ impl Index { pub type IndexThreads = keys::Any<validate::IndexThreads>; mod index_threads { - use crate::bstr::BStr; - use crate::config; - use crate::config::key::GenericErrorWithValue; - use crate::config::tree::index::IndexThreads; use std::borrow::Cow; + use crate::{ + bstr::BStr, + config, + config::{key::GenericErrorWithValue, tree::index::IndexThreads}, + }; + impl IndexThreads { /// Parse `value` into the amount of threads to use, with `1` being single-threaded, or `0` indicating /// to select the amount of threads, with any other number being the specific amount of threads to use. diff --git a/vendor/gix/src/config/tree/sections/mod.rs b/vendor/gix/src/config/tree/sections/mod.rs index 9f0a50c93..ebf24a8b7 100644 --- a/vendor/gix/src/config/tree/sections/mod.rs +++ b/vendor/gix/src/config/tree/sections/mod.rs @@ -45,6 +45,11 @@ pub mod diff; pub struct Extensions; pub mod extensions; +/// The `fetch` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Fetch; +pub mod fetch; + /// The `gitoxide` top-level section. #[derive(Copy, Clone, Default)] pub struct Gitoxide; diff --git a/vendor/gix/src/config/tree/sections/protocol.rs b/vendor/gix/src/config/tree/sections/protocol.rs index 58e907b0f..a0510f2b8 100644 --- a/vendor/gix/src/config/tree/sections/protocol.rs +++ b/vendor/gix/src/config/tree/sections/protocol.rs @@ -6,6 +6,8 @@ use crate::{ impl Protocol { /// The `protocol.allow` key. pub const ALLOW: Allow = Allow::new_with_validate("allow", &config::Tree::PROTOCOL, validate::Allow); + /// The `protocol.version` key. + pub const VERSION: Version = Version::new_with_validate("version", &config::Tree::PROTOCOL, validate::Version); /// The `protocol.<name>` subsection pub const NAME_PARAMETER: NameParameter = NameParameter; @@ -14,6 +16,9 @@ impl Protocol { /// The `protocol.allow` key type. pub type Allow = keys::Any<validate::Allow>; +/// The `protocol.version` key. +pub type Version = keys::Any<validate::Version>; + #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] mod allow { use std::borrow::Cow; @@ -39,7 +44,7 @@ mod allow { pub struct NameParameter; impl NameParameter { - /// The `credential.<url>.helper` key. + /// The `protocol.<name>.allow` key. pub const ALLOW: Allow = Allow::new_with_validate("allow", &Protocol::NAME_PARAMETER, validate::Allow); } @@ -63,7 +68,7 @@ impl Section for Protocol { } fn keys(&self) -> &[&dyn Key] { - &[&Self::ALLOW] + &[&Self::ALLOW, &Self::VERSION] } fn sub_sections(&self) -> &[&dyn Section] { @@ -71,6 +76,38 @@ impl Section for Protocol { } } +mod key_impls { + impl super::Version { + /// Convert `value` into the corresponding protocol version, possibly applying the correct default. + #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] + pub fn try_into_protocol_version( + &'static self, + value: Option<Result<i64, gix_config::value::Error>>, + ) -> Result<gix_protocol::transport::Protocol, crate::config::key::GenericErrorWithValue> { + let value = match value { + None => return Ok(gix_protocol::transport::Protocol::V2), + Some(v) => v, + }; + Ok(match value { + Ok(0) => gix_protocol::transport::Protocol::V0, + Ok(1) => gix_protocol::transport::Protocol::V1, + Ok(2) => gix_protocol::transport::Protocol::V2, + Ok(other) => { + return Err(crate::config::key::GenericErrorWithValue::from_value( + self, + other.to_string().into(), + )) + } + Err(err) => { + return Err( + crate::config::key::GenericErrorWithValue::from_value(self, "unknown".into()).with_source(err), + ) + } + }) + } + } +} + mod validate { use crate::{bstr::BStr, config::tree::keys}; @@ -82,4 +119,17 @@ mod validate { Ok(()) } } + + pub struct Version; + impl keys::Validate for Version { + fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { + let value = gix_config::Integer::try_from(value)? + .to_decimal() + .ok_or_else(|| format!("integer {value} cannot be represented as integer"))?; + match value { + 0 | 1 | 2 => Ok(()), + _ => Err(format!("protocol version {value} is unknown").into()), + } + } + } } diff --git a/vendor/gix/src/env.rs b/vendor/gix/src/env.rs index 4c61ceb4e..ce5461bcc 100644 --- a/vendor/gix/src/env.rs +++ b/vendor/gix/src/env.rs @@ -12,7 +12,7 @@ pub fn agent() -> &'static str { concat!("oxide-", env!("CARGO_PKG_VERSION")) } -/// Equivalent to `std::env::args_os()`, but with precomposed unicode on MacOS and other apple platforms. +/// Equivalent to `std::env::args_os()`, but with precomposed unicode on `MacOS` and other apple platforms. #[cfg(not(target_vendor = "apple"))] pub fn args_os() -> impl Iterator<Item = OsString> { std::env::args_os() @@ -119,7 +119,8 @@ pub mod collate { Error::Fetch( crate::remote::fetch::Error::PackThreads(_) | crate::remote::fetch::Error::PackIndexVersion(_) - | crate::remote::fetch::Error::RemovePackKeepFile { .. }, + | crate::remote::fetch::Error::RemovePackKeepFile { .. } + | crate::remote::fetch::Error::Negotiate(_), ) => true, _ => false, } diff --git a/vendor/gix/src/head/peel.rs b/vendor/gix/src/head/peel.rs index 65a876bc4..a4f7c71b0 100644 --- a/vendor/gix/src/head/peel.rs +++ b/vendor/gix/src/head/peel.rs @@ -6,7 +6,7 @@ use crate::{ mod error { use crate::{object, reference}; - /// The error returned by [Head::peel_to_id_in_place()][super::Head::peel_to_id_in_place()] and [Head::into_fully_peeled_id()][super::Head::into_fully_peeled_id()]. + /// The error returned by [`Head::peel_to_id_in_place()`][super::Head::peel_to_id_in_place()] and [`Head::into_fully_peeled_id()`][super::Head::into_fully_peeled_id()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -25,7 +25,7 @@ use crate::head::Kind; pub mod to_commit { use crate::object; - /// The error returned by [Head::peel_to_commit_in_place()][super::Head::peel_to_commit_in_place()]. + /// The error returned by [`Head::peel_to_commit_in_place()`][super::Head::peel_to_commit_in_place()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/vendor/gix/src/id.rs b/vendor/gix/src/id.rs index 72f424334..0d5c86752 100644 --- a/vendor/gix/src/id.rs +++ b/vendor/gix/src/id.rs @@ -81,7 +81,7 @@ impl<'repo> Id<'repo> { Id { inner: id.into(), repo } } - /// Turn this instance into its bare [ObjectId]. + /// Turn this instance into its bare [`ObjectId`]. pub fn detach(self) -> ObjectId { self.inner } diff --git a/vendor/gix/src/lib.rs b/vendor/gix/src/lib.rs index eb5efcfdf..5de702dbf 100644 --- a/vendor/gix/src/lib.rs +++ b/vendor/gix/src/lib.rs @@ -14,7 +14,7 @@ //! Most extensions to existing objects provide an `obj_with_extension.attach(&repo).an_easier_version_of_a_method()` for simpler //! call signatures. //! -//! ## ThreadSafe Mode +//! ## `ThreadSafe` Mode //! //! By default, the [`Repository`] isn't `Sync` and thus can't be used in certain contexts which require the `Sync` trait. //! @@ -37,7 +37,7 @@ //! //! ### Terminology //! -//! #### WorkingTree and WorkTree +//! #### `WorkingTree` and `WorkTree` //! //! When reading the documentation of the canonical gix-worktree program one gets the impression work tree and working tree are used //! interchangeably. We use the term _work tree_ only and try to do so consistently as its shorter and assumed to be the same. @@ -68,6 +68,7 @@ // APIs/instances anyway. pub use gix_actor as actor; pub use gix_attributes as attrs; +pub use gix_commitgraph as commitgraph; pub use gix_credentials as credentials; pub use gix_date as date; pub use gix_features as features; @@ -80,6 +81,7 @@ pub use gix_ignore as ignore; #[doc(inline)] pub use gix_index as index; pub use gix_lock as lock; +pub use gix_negotiate as negotiate; pub use gix_object as objs; pub use gix_object::bstr; pub use gix_odb as odb; @@ -119,8 +121,7 @@ pub(crate) type Config = OwnShared<gix_config::File<'static>>; /// mod types; pub use types::{ - Commit, Head, Id, Kind, Object, ObjectDetached, Reference, Remote, Repository, Tag, ThreadSafeRepository, Tree, - Worktree, + Commit, Head, Id, Object, ObjectDetached, Reference, Remote, Repository, Tag, ThreadSafeRepository, Tree, Worktree, }; /// @@ -130,7 +131,7 @@ pub mod head; pub mod id; pub mod object; pub mod reference; -mod repository; +pub mod repository; pub mod tag; /// @@ -139,7 +140,7 @@ pub mod progress; /// pub mod diff; -/// See [ThreadSafeRepository::discover()], but returns a [`Repository`] instead. +/// See [`ThreadSafeRepository::discover()`], but returns a [`Repository`] instead. /// /// # Note /// @@ -154,13 +155,13 @@ pub fn discover(directory: impl AsRef<std::path::Path>) -> Result<Repository, di ThreadSafeRepository::discover(directory).map(Into::into) } -/// See [ThreadSafeRepository::init()], but returns a [`Repository`] instead. +/// See [`ThreadSafeRepository::init()`], but returns a [`Repository`] instead. #[allow(clippy::result_large_err)] pub fn init(directory: impl AsRef<std::path::Path>) -> Result<Repository, init::Error> { ThreadSafeRepository::init(directory, create::Kind::WithWorktree, create::Options::default()).map(Into::into) } -/// See [ThreadSafeRepository::init()], but returns a [`Repository`] instead. +/// See [`ThreadSafeRepository::init()`], but returns a [`Repository`] instead. #[allow(clippy::result_large_err)] pub fn init_bare(directory: impl AsRef<std::path::Path>) -> Result<Repository, init::Error> { ThreadSafeRepository::init(directory, create::Kind::Bare, create::Options::default()).map(Into::into) @@ -169,7 +170,7 @@ pub fn init_bare(directory: impl AsRef<std::path::Path>) -> Result<Repository, i /// Create a platform for configuring a bare clone from `url` to the local `path`, using default options for opening it (but /// amended with using configuration from the git installation to ensure all authentication options are honored). /// -/// See [`clone::PrepareFetch::new()] for a function to take full control over all options. +/// See [`clone::PrepareFetch::new()`] for a function to take full control over all options. #[allow(clippy::result_large_err)] pub fn prepare_clone_bare<Url, E>( url: Url, @@ -191,7 +192,7 @@ where /// Create a platform for configuring a clone with main working tree from `url` to the local `path`, using default options for opening it /// (but amended with using configuration from the git installation to ensure all authentication options are honored). /// -/// See [`clone::PrepareFetch::new()] for a function to take full control over all options. +/// See [`clone::PrepareFetch::new()`] for a function to take full control over all options. #[allow(clippy::result_large_err)] pub fn prepare_clone<Url, E>(url: Url, path: impl AsRef<std::path::Path>) -> Result<clone::PrepareFetch, clone::Error> where @@ -214,13 +215,13 @@ fn open_opts_with_git_binary_config() -> open::Options { opts } -/// See [ThreadSafeRepository::open()], but returns a [`Repository`] instead. +/// See [`ThreadSafeRepository::open()`], but returns a [`Repository`] instead. #[allow(clippy::result_large_err)] pub fn open(directory: impl Into<std::path::PathBuf>) -> Result<Repository, open::Error> { ThreadSafeRepository::open(directory).map(Into::into) } -/// See [ThreadSafeRepository::open_opts()], but returns a [`Repository`] instead. +/// See [`ThreadSafeRepository::open_opts()`], but returns a [`Repository`] instead. #[allow(clippy::result_large_err)] pub fn open_opts(directory: impl Into<std::path::PathBuf>, options: open::Options) -> Result<Repository, open::Error> { ThreadSafeRepository::open_opts(directory, options).map(Into::into) @@ -259,5 +260,3 @@ pub mod shallow; pub mod discover; pub mod env; - -mod kind; diff --git a/vendor/gix/src/object/tree/diff/for_each.rs b/vendor/gix/src/object/tree/diff/for_each.rs index 5cae4cf2f..a72033182 100644 --- a/vendor/gix/src/object/tree/diff/for_each.rs +++ b/vendor/gix/src/object/tree/diff/for_each.rs @@ -209,9 +209,9 @@ where fn visit(&mut self, change: gix_diff::tree::visit::Change) -> gix_diff::tree::visit::Action { match self.tracked.as_mut() { - Some(tracked) => tracked - .try_push_change(change, self.recorder.path()) - .map(|change| { + Some(tracked) => tracked.try_push_change(change, self.recorder.path()).map_or( + gix_diff::tree::visit::Action::Continue, + |change| { Self::emit_change( change, self.recorder.path(), @@ -220,8 +220,8 @@ where self.other_repo, &mut self.err, ) - }) - .unwrap_or(gix_diff::tree::visit::Action::Continue), + }, + ), None => Self::emit_change( change, self.recorder.path(), diff --git a/vendor/gix/src/object/tree/diff/rewrites.rs b/vendor/gix/src/object/tree/diff/rewrites.rs index 304894d15..1502048ec 100644 --- a/vendor/gix/src/object/tree/diff/rewrites.rs +++ b/vendor/gix/src/object/tree/diff/rewrites.rs @@ -49,7 +49,7 @@ pub struct Outcome { pub num_similarity_checks_skipped_for_copy_tracking_due_to_limit: usize, } -/// The error returned by [`Rewrites::try_from_config()]. +/// The error returned by [`Rewrites::try_from_config()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/vendor/gix/src/object/tree/diff/tracked.rs b/vendor/gix/src/object/tree/diff/tracked.rs index 3bbe01624..d6042fcbc 100644 --- a/vendor/gix/src/object/tree/diff/tracked.rs +++ b/vendor/gix/src/object/tree/diff/tracked.rs @@ -311,7 +311,7 @@ fn needs_exact_match(percentage: Option<f32>) -> bool { percentage.map_or(true, |p| p >= 1.0) } -/// <src_idx, src, possibly diff stat> +/// <`src_idx`, src, possibly diff stat> type SourceTuple<'a> = (usize, &'a Item, Option<DiffLineStats>); /// Find `item` in our set of items ignoring `item_idx` to avoid finding ourselves, by similarity indicated by `percentage`. @@ -337,8 +337,7 @@ fn find_match<'a>( let end = items .iter() .position(|a| a.change.oid() != item_id) - .map(|idx| first_idx + idx) - .unwrap_or(items.len()); + .map_or(items.len(), |idx| first_idx + idx); first_idx..end }) { Some(range) => range, diff --git a/vendor/gix/src/open/permissions.rs b/vendor/gix/src/open/permissions.rs index 633575a9d..6a72beef2 100644 --- a/vendor/gix/src/open/permissions.rs +++ b/vendor/gix/src/open/permissions.rs @@ -1,7 +1,8 @@ //! Various permissions to define what can be done when operating a [`Repository`][crate::Repository]. -use crate::open::Permissions; use gix_sec::Trust; +use crate::open::Permissions; + /// Configure from which sources git configuration may be loaded. /// /// Note that configuration from inside of the repository is always loaded as it's definitely required for correctness. diff --git a/vendor/gix/src/open/repository.rs b/vendor/gix/src/open/repository.rs index c7702b5f6..e89fdc430 100644 --- a/vendor/gix/src/open/repository.rs +++ b/vendor/gix/src/open/repository.rs @@ -284,12 +284,12 @@ fn replacement_objects_refs_prefix( mut filter_config_section: fn(&gix_config::file::Metadata) -> bool, ) -> Result<Option<PathBuf>, Error> { let is_disabled = config - .boolean_filter_by_key("gitoxide.objects.noReplace", &mut filter_config_section) - .map(|b| gitoxide::Objects::NO_REPLACE.enrich_error(b)) + .boolean_filter_by_key("core.useReplaceRefs", &mut filter_config_section) + .map(|b| Core::USE_REPLACE_REFS.enrich_error(b)) .transpose() .with_leniency(lenient) .map_err(config::Error::ConfigBoolean)? - .unwrap_or_default(); + .unwrap_or(true); if is_disabled { return Ok(None); diff --git a/vendor/gix/src/reference/edits.rs b/vendor/gix/src/reference/edits.rs index aadd087ba..c6510c2e0 100644 --- a/vendor/gix/src/reference/edits.rs +++ b/vendor/gix/src/reference/edits.rs @@ -25,7 +25,7 @@ pub mod set_target_id { /// Note that the operation will fail on symbolic references, to change their type use the lower level reference database, /// or if the reference was deleted or changed in the mean time. /// Furthermore, refrain from using this method for more than a one-off change as it creates a transaction for each invocation. - /// If multiple reference should be changed, use [Repository::edit_references()][crate::Repository::edit_references()] + /// If multiple reference should be changed, use [`Repository::edit_references()`][crate::Repository::edit_references()] /// or the lower level reference database instead. #[allow(clippy::result_large_err)] pub fn set_target_id( diff --git a/vendor/gix/src/reference/errors.rs b/vendor/gix/src/reference/errors.rs index 364456fd1..a931b3974 100644 --- a/vendor/gix/src/reference/errors.rs +++ b/vendor/gix/src/reference/errors.rs @@ -2,7 +2,7 @@ pub mod edit { use crate::config; - /// The error returned by [edit_references(…)][crate::Repository::edit_references()], and others + /// The error returned by [`edit_references(…)`][crate::Repository::edit_references()], and others /// which ultimately create a reference. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] @@ -22,8 +22,8 @@ pub mod edit { /// pub mod peel { - /// The error returned by [Reference::peel_to_id_in_place(…)][crate::Reference::peel_to_id_in_place()] and - /// [Reference::into_fully_peeled_id(…)][crate::Reference::into_fully_peeled_id()]. + /// The error returned by [`Reference::peel_to_id_in_place(…)`][crate::Reference::peel_to_id_in_place()] and + /// [`Reference::into_fully_peeled_id(…)`][crate::Reference::into_fully_peeled_id()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -36,7 +36,7 @@ pub mod peel { /// pub mod head_id { - /// The error returned by [Repository::head_id(…)][crate::Repository::head_id()]. + /// The error returned by [`Repository::head_id(…)`][crate::Repository::head_id()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -51,7 +51,7 @@ pub mod head_id { /// pub mod head_commit { - /// The error returned by [Repository::head_commit(…)][crate::Repository::head_commit()]. + /// The error returned by [`Repository::head_commit`(…)][crate::Repository::head_commit()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -66,7 +66,7 @@ pub mod head_commit { pub mod find { /// pub mod existing { - /// The error returned by [find_reference(…)][crate::Repository::find_reference()], and others. + /// The error returned by [`find_reference(…)`][crate::Repository::find_reference()], and others. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -77,7 +77,7 @@ pub mod find { } } - /// The error returned by [try_find_reference(…)][crate::Repository::try_find_reference()]. + /// The error returned by [`try_find_reference(…)`][crate::Repository::try_find_reference()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { diff --git a/vendor/gix/src/reference/remote.rs b/vendor/gix/src/reference/remote.rs index dd96892e2..d0c566302 100644 --- a/vendor/gix/src/reference/remote.rs +++ b/vendor/gix/src/reference/remote.rs @@ -4,7 +4,7 @@ use crate::{config, config::tree::Branch, remote, Reference}; impl<'repo> Reference<'repo> { /// Find the unvalidated name of our remote for `direction` as configured in `branch.<name>.remote|pushRemote` respectively. /// If `Some(<name>)` it can be used in [`Repository::find_remote(…)`][crate::Repository::find_remote()], or if `None` then - /// [Repository::remote_default_name()][crate::Repository::remote_default_name()] could be used in its place. + /// [`Repository::remote_default_name()`][crate::Repository::remote_default_name()] could be used in its place. /// /// Return `None` if no remote is configured. /// diff --git a/vendor/gix/src/remote/connect.rs b/vendor/gix/src/remote/connect.rs index 3dbd93486..63475b7c5 100644 --- a/vendor/gix/src/remote/connect.rs +++ b/vendor/gix/src/remote/connect.rs @@ -24,8 +24,8 @@ mod error { Connect(#[from] gix_protocol::transport::client::connect::Error), #[error("The {} url was missing - don't know where to establish a connection to", direction.as_str())] MissingUrl { direction: remote::Direction }, - #[error("Protocol named {given:?} is not a valid protocol. Choose between 1 and 2")] - UnknownProtocol { given: BString }, + #[error("The given protocol version was invalid. Choose between 1 and 2")] + UnknownProtocol { source: config::key::GenericErrorWithValue }, #[error("Could not verify that \"{}\" url is a valid git directory before attempting to use it", url.to_bstring())] FileUrl { source: Box<gix_discover::is_git::Error>, @@ -128,25 +128,9 @@ impl<'repo> Remote<'repo> { Ok(url) } - use gix_protocol::transport::Protocol; - let version = self - .repo - .config - .resolved - .integer("protocol", None, "version") - .unwrap_or(Ok(2)) - .map_err(|err| Error::UnknownProtocol { given: err.input }) - .and_then(|num| { - Ok(match num { - 1 => Protocol::V1, - 2 => Protocol::V2, - num => { - return Err(Error::UnknownProtocol { - given: num.to_string().into(), - }) - } - }) - })?; + let version = crate::config::tree::Protocol::VERSION + .try_into_protocol_version(self.repo.config.resolved.integer("protocol", None, "version")) + .map_err(|err| Error::UnknownProtocol { source: err })?; let url = self.url(direction).ok_or(Error::MissingUrl { direction })?.to_owned(); if !self.repo.config.url_scheme()?.allow(&url.scheme) { diff --git a/vendor/gix/src/remote/connection/access.rs b/vendor/gix/src/remote/connection/access.rs index eba603da0..932fdb09d 100644 --- a/vendor/gix/src/remote/connection/access.rs +++ b/vendor/gix/src/remote/connection/access.rs @@ -13,7 +13,7 @@ impl<'a, 'repo, T> Connection<'a, 'repo, T> { /// /// A custom function may also be used to prevent accessing resources with authentication. /// - /// Use the [configured_credentials()][Connection::configured_credentials()] method to obtain the implementation + /// Use the [`configured_credentials()`][Connection::configured_credentials()] method to obtain the implementation /// that would otherwise be used, which can be useful to proxy the default configuration and obtain information about the /// URLs to authenticate with. pub fn with_credentials( diff --git a/vendor/gix/src/remote/connection/fetch/error.rs b/vendor/gix/src/remote/connection/fetch/error.rs index afcacca13..5034dcb5d 100644 --- a/vendor/gix/src/remote/connection/fetch/error.rs +++ b/vendor/gix/src/remote/connection/fetch/error.rs @@ -43,6 +43,8 @@ pub enum Error { RejectShallowRemoteConfig(#[from] config::boolean::Error), #[error("Receiving objects from shallow remotes is prohibited due to the value of `clone.rejectShallow`")] RejectShallowRemote, + #[error(transparent)] + NegotiationAlgorithmConfig(#[from] config::key::GenericErrorWithValue), } 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 a51ae7c54..b4fe00935 100644 --- a/vendor/gix/src/remote/connection/fetch/mod.rs +++ b/vendor/gix/src/remote/connection/fetch/mod.rs @@ -43,6 +43,8 @@ impl RefLogMessage { pub enum Status { /// Nothing changed as the remote didn't have anything new compared to our tracking branches, thus no pack was received /// and no new object was added. + /// + /// As we could determine that nothing changed without remote interaction, there was no negotiation at all. NoPackReceived { /// However, depending on the refspecs, references might have been updated nonetheless to point to objects as /// reported by the remote. @@ -50,6 +52,8 @@ pub enum Status { }, /// There was at least one tip with a new object which we received. Change { + /// The number of rounds it took to minimize the pack to contain only the objects we don't have. + negotiation_rounds: usize, /// Information collected while writing the pack and its index. write_pack_bundle: gix_pack::bundle::write::Outcome, /// Information collected while updating references. @@ -58,6 +62,8 @@ pub enum Status { /// A dry run was performed which leaves the local repository without any change /// nor will a pack have been received. DryRun { + /// The number of rounds it took to minimize the *would-be-sent*-pack to contain only the objects we don't have. + negotiation_rounds: usize, /// Information about what updates to refs would have been done. update_refs: refs::update::Outcome, }, @@ -91,8 +97,7 @@ impl From<ProgressId> for gix_features::progress::Id { } } -/// -pub mod negotiate; +pub(crate) mod negotiate; /// pub mod prepare { @@ -158,7 +163,7 @@ impl<'remote, 'repo, T> Prepare<'remote, 'repo, T> where T: Transport, { - /// Return the ref_map (that includes the server handshake) which was part of listing refs prior to fetching a pack. + /// Return the `ref_map` (that includes the server handshake) which was part of listing refs prior to fetching a pack. pub fn ref_map(&self) -> &RefMap { &self.ref_map } diff --git a/vendor/gix/src/remote/connection/fetch/negotiate.rs b/vendor/gix/src/remote/connection/fetch/negotiate.rs index 771c5acba..e94461bab 100644 --- a/vendor/gix/src/remote/connection/fetch/negotiate.rs +++ b/vendor/gix/src/remote/connection/fetch/negotiate.rs @@ -1,11 +1,12 @@ -use crate::remote::fetch; +use std::borrow::Cow; -/// The way the negotiation is performed -#[derive(Copy, Clone)] -pub(crate) enum Algorithm { - /// Our very own implementation that probably should be replaced by one of the known algorithms soon. - Naive, -} +use gix_negotiate::Flags; +use gix_odb::HeaderExt; +use gix_pack::Find; + +use crate::remote::{fetch, fetch::Shallow}; + +type Queue = gix_revision::PriorityQueue<gix_revision::graph::CommitterTimestamp, gix_hash::ObjectId>; /// The error returned during negotiation. #[derive(Debug, thiserror::Error)] @@ -13,77 +14,336 @@ pub(crate) enum Algorithm { pub enum Error { #[error("We were unable to figure out what objects the server should send after {rounds} round(s)")] NegotiationFailed { rounds: usize }, + #[error(transparent)] + LookupCommitInGraph(#[from] gix_revision::graph::lookup::commit::Error), + #[error(transparent)] + InitRefsIterator(#[from] crate::reference::iter::init::Error), + #[error(transparent)] + InitRefsIteratorPlatform(#[from] crate::reference::iter::Error), + #[error(transparent)] + ObtainRefDuringIteration(#[from] Box<dyn std::error::Error + Send + Sync + 'static>), + #[error(transparent)] + LoadIndex(#[from] gix_odb::store::load_index::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, +#[must_use] +pub(crate) enum Action { + /// None of the remote refs moved compared to our last recorded state (via tracking refs), so there is nothing to do at all, + /// not even a ref update. + NoChange, + /// Don't negotiate, don't fetch the pack, skip right to updating the references. + /// + /// This happens if we already have all local objects even though the server seems to have changed. + SkipToRefUpdate, + /// We can't know for sure if fetching *is not* needed, so we go ahead and negotiate. + MustNegotiate { + /// Each `ref_map.mapping` has a slot here which is `true` if we have the object the remote ref points to locally. + remote_ref_target_known: Vec<bool>, + }, +} + +/// This function is modeled after the similarly named one in the git codebase to do the following: +/// +/// * figure out all advertised refs on the remote *that we already have* and keep track of the oldest one as cutoff date. +/// * mark all of our own refs as tips for a traversal. +/// * mark all their parents, recursively, up to (and including) the cutoff date up to which we have seen the servers commit that we have. +/// * pass all known-to-be-common-with-remote commits to the negotiator as common commits. +/// +/// This is done so that we already find the most recent common commits, even if we are ahead, which is *potentially* better than +/// what we would get if we would rely on tracking refs alone, particularly if one wouldn't trust the tracking refs for some reason. +/// +/// Note that git doesn't trust its own tracking refs as the server *might* have changed completely, for instance by force-pushing, so +/// marking our local tracking refs as known is something that's actually not proven to be correct so it's not done. +/// +/// Additionally, it does what's done in `transport.c` and we check if a fetch is actually needed as at least one advertised ref changed. +/// +/// Finally, we also mark tips in the `negotiator` in one go to avoid traversing all refs twice, since we naturally encounter all tips during +/// our own walk. +/// +/// Return whether or not we should negotiate, along with a queue for later use. +pub(crate) fn mark_complete_and_common_ref( repo: &crate::Repository, - ref_map: &crate::remote::fetch::RefMap, - 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 { + negotiator: &mut dyn gix_negotiate::Negotiator, + graph: &mut gix_negotiate::Graph<'_>, + ref_map: &fetch::RefMap, + shallow: &fetch::Shallow, +) -> Result<Action, Error> { + if let 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); + return Ok(Action::NoChange); + } + if let Some(fetch::Mapping { + remote: fetch::Source::Ref(gix_protocol::handshake::Ref::Unborn { .. }), + .. + }) = ref_map.mappings.last().filter(|_| ref_map.mappings.len() == 1) + { + // There is only an unborn branch, as the remote has an empty repository. This means there is nothing to do except for + // possibly reproducing the unborn branch locally. + return Ok(Action::SkipToRefUpdate); } - match algo { - Algorithm::Naive => { - 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| { - mapping - .spec_index - .implicit_index() - .and_then(|idx| ref_map.extra_refspecs.get(idx)) - .map_or(false, |spec| spec.to_ref() == tag_spec) - }) { - continue; - } - let have_id = mapping.local.as_ref().and_then(|name| { - repo.find_reference(name) - .ok() - .and_then(|r| r.target().try_id().map(ToOwned::to_owned)) - }); - match have_id { - Some(have_id) => { - if let Some(want_id) = mapping.remote.as_id() { - if want_id != have_id { - arguments.want(want_id); - arguments.have(have_id); - } - } - } - None => { - if let Some(want_id) = mapping.remote.as_id() { - arguments.want(want_id); - has_missing_tracking_branch = true; - } - } - } + // Compute the cut-off date by checking which of the refs advertised (and matched in refspecs) by the remote we have, + // and keep the oldest one. + let mut cutoff_date = None::<gix_revision::graph::CommitterTimestamp>; + let mut num_mappings_with_change = 0; + let mut remote_ref_target_known: Vec<bool> = std::iter::repeat(false).take(ref_map.mappings.len()).collect(); + + for (mapping_idx, mapping) in ref_map.mappings.iter().enumerate() { + let want_id = mapping.remote.as_id(); + let have_id = mapping.local.as_ref().and_then(|name| { + // this is the only time git uses the peer-id. + let r = repo.find_reference(name).ok()?; + r.target().try_id().map(ToOwned::to_owned) + }); + + // Like git, we don't let known unchanged mappings participate in the tree traversal + if want_id.zip(have_id).map_or(true, |(want, have)| want != have) { + num_mappings_with_change += 1; + } + + if let Some(commit) = want_id + .and_then(|id| graph.try_lookup_or_insert_commit(id.into(), |_| {}).transpose()) + .transpose()? + { + remote_ref_target_known[mapping_idx] = true; + cutoff_date = cutoff_date.unwrap_or_default().max(commit.commit_time).into(); + } else if want_id.map_or(false, |maybe_annotated_tag| repo.objects.contains(maybe_annotated_tag)) { + remote_ref_target_known[mapping_idx] = true; + } + } + + // If any kind of shallowing operation is desired, the server may still create a pack for us. + if matches!(shallow, Shallow::NoChange) { + if num_mappings_with_change == 0 { + return Ok(Action::NoChange); + } else if remote_ref_target_known.iter().all(|known| *known) { + return Ok(Action::SkipToRefUpdate); + } + } + + // color our commits as complete as identified by references, unconditionally + // (`git` is conditional here based on `deepen`, but it doesn't make sense and it's hard to extract from history when that happened). + let mut queue = Queue::new(); + mark_all_refs_in_repo(repo, graph, &mut queue, Flags::COMPLETE)?; + mark_alternate_complete(repo, graph, &mut queue)?; + // Keep track of the tips, which happen to be on our queue right, before we traverse the graph with cutoff. + let tips = if let Some(cutoff) = cutoff_date { + let tips = Cow::Owned(queue.clone()); + // color all their parents up to the cutoff date, the oldest commit we know the server has. + mark_recent_complete_commits(&mut queue, graph, cutoff)?; + tips + } else { + Cow::Borrowed(&queue) + }; + + // mark all complete advertised refs as common refs. + for mapping in ref_map + .mappings + .iter() + .zip(remote_ref_target_known.iter().copied()) + // We need this filter as the graph wouldn't contain annotated tags. + .filter_map(|(mapping, known)| (!known).then_some(mapping)) + { + let want_id = mapping.remote.as_id(); + if let Some(common_id) = want_id + .and_then(|id| graph.get(id).map(|c| (c, id))) + .filter(|(c, _)| c.data.flags.contains(Flags::COMPLETE)) + .map(|(_, id)| id) + { + negotiator.known_common(common_id.into(), graph)?; + } + } + + // As negotiators currently may rely on getting `known_common` calls first and tips after, we adhere to that which is the only + // reason we cached the set of tips. + for tip in tips.iter_unordered() { + negotiator.add_tip(*tip, graph)?; + } + + Ok(Action::MustNegotiate { + remote_ref_target_known, + }) +} + +/// Add all `wants` to `arguments`, which is the unpeeled direct target that the advertised remote ref points to. +pub(crate) fn add_wants( + repo: &crate::Repository, + arguments: &mut gix_protocol::fetch::Arguments, + ref_map: &fetch::RefMap, + mapping_known: &[bool], + shallow: &fetch::Shallow, + fetch_tags: fetch::Tags, +) { + // With included tags, we have to keep mappings of tags to handle them later when updating refs, but we don't want to + // explicitly `want` them as the server will determine by itself which tags are pointing to a commit it wants to send. + // If we would not exclude implicit tag mappings like this, we would get too much of the graph. + let tag_refspec_to_ignore = matches!(fetch_tags, crate::remote::fetch::Tags::Included) + .then(|| fetch_tags.to_refspec()) + .flatten(); + + // When using shallow, we can't exclude `wants` as the remote won't send anything then. Thus we have to resend everything + // we have as want instead to get exactly the same graph, but possibly deepened. + let is_shallow = !matches!(shallow, fetch::Shallow::NoChange); + let wants = ref_map + .mappings + .iter() + .zip(mapping_known) + .filter_map(|(m, known)| (is_shallow || !*known).then_some(m)); + for want in wants { + // Here we ignore implicit tag mappings if needed. + if tag_refspec_to_ignore.map_or(false, |tag_spec| { + want.spec_index + .implicit_index() + .and_then(|idx| ref_map.extra_refspecs.get(idx)) + .map_or(false, |spec| spec.to_ref() == tag_spec) + }) { + continue; + } + let id_on_remote = want.remote.as_id(); + if !arguments.can_use_ref_in_want() || matches!(want.remote, fetch::Source::ObjectId(_)) { + if let Some(id) = id_on_remote { + arguments.want(id); } + } else { + arguments.want_ref( + want.remote + .as_name() + .expect("name available if this isn't an object id"), + ) + } + let id_is_annotated_tag_we_have = id_on_remote + .and_then(|id| repo.objects.header(id).ok().map(|h| (id, h))) + .filter(|(_, h)| h.kind() == gix_object::Kind::Tag) + .map(|(id, _)| id); + if let Some(tag_on_remote) = id_is_annotated_tag_we_have { + // Annotated tags are not handled at all by negotiators in the commit-graph - they only see commits and thus won't + // ever add `have`s for tags. To correct for that, we add these haves here to avoid getting them sent again. + arguments.have(tag_on_remote) + } + } +} - 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); +/// Remove all commits that are more recent than the cut-off, which is the commit time of the oldest common commit we have with the server. +fn mark_recent_complete_commits( + queue: &mut Queue, + graph: &mut gix_negotiate::Graph<'_>, + cutoff: gix_revision::graph::CommitterTimestamp, +) -> Result<(), Error> { + while let Some(id) = queue + .peek() + .and_then(|(commit_time, id)| (commit_time >= &cutoff).then_some(*id)) + { + queue.pop(); + let commit = graph.get(&id).expect("definitely set when adding tips or parents"); + for parent_id in commit.parents.clone() { + let mut was_complete = false; + if let Some(parent) = graph + .try_lookup_or_insert_commit(parent_id, |md| { + was_complete = md.flags.contains(Flags::COMPLETE); + md.flags |= Flags::COMPLETE + })? + .filter(|_| !was_complete) + { + queue.insert(parent.commit_time, parent_id) + } + } + } + Ok(()) +} + +fn mark_all_refs_in_repo( + repo: &crate::Repository, + graph: &mut gix_negotiate::Graph<'_>, + queue: &mut Queue, + mark: Flags, +) -> Result<(), Error> { + for local_ref in repo.references()?.all()?.peeled() { + let local_ref = local_ref?; + let id = local_ref.id().detach(); + let mut is_complete = false; + if let Some(commit) = graph + .try_lookup_or_insert_commit(id, |md| { + is_complete = md.flags.contains(Flags::COMPLETE); + md.flags |= mark + })? + .filter(|_| !is_complete) + { + queue.insert(commit.commit_time, id); + }; + } + Ok(()) +} + +fn mark_alternate_complete( + repo: &crate::Repository, + graph: &mut gix_negotiate::Graph<'_>, + queue: &mut Queue, +) -> Result<(), Error> { + for alternate_repo in repo + .objects + .store_ref() + .alternate_db_paths()? + .into_iter() + .filter_map(|path| { + path.ancestors() + .nth(1) + .and_then(|git_dir| crate::open_opts(git_dir, repo.options.clone()).ok()) + }) + { + mark_all_refs_in_repo(&alternate_repo, graph, queue, Flags::ALTERNATE | Flags::COMPLETE)?; + } + Ok(()) +} + +/// Negotiate the nth `round` with `negotiator` sending `haves_to_send` after possibly making the known common commits +/// as sent by the remote known to `negotiator` using `previous_response` if this isn't the first round. +/// All `haves` are added to `arguments` accordingly. +/// Returns the amount of haves actually sent. +pub(crate) fn one_round( + negotiator: &mut dyn gix_negotiate::Negotiator, + graph: &mut gix_negotiate::Graph<'_>, + haves_to_send: usize, + arguments: &mut gix_protocol::fetch::Arguments, + previous_response: Option<&gix_protocol::fetch::Response>, + mut common: Option<&mut Vec<gix_hash::ObjectId>>, +) -> Result<(usize, bool), Error> { + let mut seen_ack = false; + if let Some(response) = previous_response { + use gix_protocol::fetch::response::Acknowledgement; + for ack in response.acknowledgements() { + match ack { + Acknowledgement::Common(id) => { + seen_ack = true; + negotiator.in_common_with_remote(*id, graph)?; + if let Some(ref mut common) = common { + common.push(*id); } } + Acknowledgement::Ready => { + // NOTE: In git, there is some logic dealing with whether to expect a DELIM or FLUSH package, + // but we handle this with peeking. + } + Acknowledgement::Nak => {} } - Ok(true) } } + + // `common` is set only if this is a stateless transport, and we repeat previously confirmed common commits as HAVE, because + // we are not going to repeat them otherwise. + if let Some(common) = common { + for have_id in common { + arguments.have(have_id); + } + } + + let mut haves_sent = 0; + for have_id in (0..haves_to_send).map_while(|_| negotiator.next_have(graph)) { + arguments.have(have_id?); + haves_sent += 1; + } + // Note that we are differing from the git implementation, which does an extra-round of with no new haves sent at all. + // For us it seems better to just say we are done when we know we are done, as potentially additional acks won't affect the + // queue of any of our implementation at all (so the negotiator won't come up with more haves next time either). + Ok((haves_sent, seen_ack)) } diff --git a/vendor/gix/src/remote/connection/fetch/receive_pack.rs b/vendor/gix/src/remote/connection/fetch/receive_pack.rs index 99560fbca..7837a9d3a 100644 --- a/vendor/gix/src/remote/connection/fetch/receive_pack.rs +++ b/vendor/gix/src/remote/connection/fetch/receive_pack.rs @@ -1,18 +1,26 @@ -use std::sync::atomic::{AtomicBool, Ordering}; +use std::{ + ops::DerefMut, + sync::atomic::{AtomicBool, Ordering}, +}; -use gix_odb::FindExt; +use gix_odb::{store::RefreshMode, FindExt}; use gix_protocol::{ fetch::Arguments, transport::{client::Transport, packetline::read::ProgressAction}, }; use crate::{ - config::tree::Clone, + config::{ + cache::util::ApplyLeniency, + tree::{Clone, Fetch, Key}, + }, remote, remote::{ connection::fetch::config, fetch, - fetch::{negotiate, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Shallow, Status}, + fetch::{ + negotiate, negotiate::Algorithm, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Shallow, Status, + }, }, Progress, Repository, }; @@ -99,9 +107,6 @@ where } 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; - if self.ref_map.object_hash != repo.object_hash() { return Err(Error::IncompatibleObjectHash { local: repo.object_hash(), @@ -109,118 +114,161 @@ where }); } - let reader = 'negotiation: loop { - progress.step(); - progress.set_name(format!("negotiate (round {round})")); - - let is_done = match negotiate::one_round( - negotiate::Algorithm::Naive, - round, - repo, - &self.ref_map, - 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(); - let update_refs = refs::update( - repo, - self.reflog_message - .take() - .unwrap_or_else(|| RefLogMessage::Prefixed { action: "fetch".into() }), - &self.ref_map.mappings, - con.remote.refspecs(remote::Direction::Fetch), - &self.ref_map.extra_refspecs, - con.remote.fetch_tags, - self.dry_run, - self.write_packed_refs, - )?; - return Ok(Outcome { - ref_map: std::mem::take(&mut self.ref_map), - status: Status::NoPackReceived { update_refs }, - }); - } - Ok(is_done) => is_done, - Err(err) => { - gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); - return Err(err.into()); - } - }; - round += 1; - let mut reader = arguments.send(&mut con.transport, is_done).await?; - if sideband_all { - 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, should_interrupt); - } - previous_response = Some(response); - break 'negotiation reader; - } else { - previous_response = Some(response); - } + let mut negotiator = repo + .config + .resolved + .string_by_key(Fetch::NEGOTIATION_ALGORITHM.logical_name().as_str()) + .map(|n| Fetch::NEGOTIATION_ALGORITHM.try_into_negotiation_algorithm(n)) + .transpose() + .with_leniency(repo.config.lenient_config)? + .unwrap_or(Algorithm::Consecutive) + .into_negotiator(); + let graph_repo = { + let mut r = repo.clone(); + // assure that checking for unknown server refs doesn't trigger ODB refreshes. + r.objects.refresh = RefreshMode::Never; + // we cache everything of importance in the graph and thus don't need an object cache. + r.objects.unset_object_cache(); + r }; - 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); + let mut graph = graph_repo.commit_graph(); + let action = negotiate::mark_complete_and_common_ref( + &graph_repo, + negotiator.deref_mut(), + &mut graph, + &self.ref_map, + &self.shallow, + )?; + let mut previous_response = None::<gix_protocol::fetch::Response>; + let mut round = 1; + let mut write_pack_bundle = match &action { + negotiate::Action::NoChange | negotiate::Action::SkipToRefUpdate => { + gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); + None } - shallow_lock = acquire_shallow_lock(repo).map(Some)?; - } + negotiate::Action::MustNegotiate { + remote_ref_target_known, + } => { + negotiate::add_wants( + repo, + &mut arguments, + &self.ref_map, + remote_ref_target_known, + &self.shallow, + con.remote.fetch_tags, + ); + let is_stateless = + arguments.is_stateless(!con.transport.connection_persists_across_multiple_requests()); + let mut haves_to_send = gix_negotiate::window_size(is_stateless, None); + let mut seen_ack = false; + let mut in_vain = 0; + let mut common = is_stateless.then(Vec::new); + let reader = 'negotiation: loop { + progress.step(); + progress.set_name(format!("negotiate (round {round})")); - let options = gix_pack::bundle::write::Options { - thread_limit: config::index_threads(repo)?, - index_version: config::pack_index_version(repo)?, - iteration_mode: gix_pack::data::input::Mode::Verify, - object_hash: con.remote.repo.object_hash(), - }; + let is_done = match negotiate::one_round( + negotiator.deref_mut(), + &mut graph, + haves_to_send, + &mut arguments, + previous_response.as_ref(), + common.as_mut(), + ) { + Ok((haves_sent, ack_seen)) => { + if ack_seen { + in_vain = 0; + } + seen_ack |= ack_seen; + in_vain += haves_sent; + let is_done = haves_sent != haves_to_send || (seen_ack && in_vain >= 256); + haves_to_send = gix_negotiate::window_size(is_stateless, haves_to_send); + is_done + } + Err(err) => { + gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); + return Err(err.into()); + } + }; + let mut reader = arguments.send(&mut con.transport, is_done).await?; + if sideband_all { + setup_remote_progress(progress, &mut reader, should_interrupt); + } + let response = + gix_protocol::fetch::Response::from_line_reader(protocol_version, &mut reader, is_done).await?; + let has_pack = response.has_pack(); + previous_response = Some(response); + if has_pack { + progress.step(); + progress.set_name("receiving pack"); + if !sideband_all { + setup_remote_progress(progress, &mut reader, should_interrupt); + } + break 'negotiation reader; + } else { + round += 1; + } + }; + drop(graph); + drop(graph_repo); + 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 mut write_pack_bundle = if matches!(self.dry_run, fetch::DryRun::No) { - Some(gix_pack::Bundle::write_to_directory( - #[cfg(feature = "async-network-client")] - { - gix_protocol::futures_lite::io::BlockOn::new(reader) - }, - #[cfg(not(feature = "async-network-client"))] - { - reader - }, - Some(repo.objects.store_ref().path().join("pack")), - progress, - should_interrupt, - Some(Box::new({ - let repo = repo.clone(); - move |oid, buf| repo.objects.find(oid, buf).ok() - })), - options, - )?) - } else { - drop(reader); - None - }; + let options = gix_pack::bundle::write::Options { + thread_limit: config::index_threads(repo)?, + index_version: config::pack_index_version(repo)?, + iteration_mode: gix_pack::data::input::Mode::Verify, + object_hash: con.remote.repo.object_hash(), + }; - if matches!(protocol_version, gix_protocol::transport::Protocol::V2) { - gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); - } + let write_pack_bundle = if matches!(self.dry_run, fetch::DryRun::No) { + Some(gix_pack::Bundle::write_to_directory( + #[cfg(feature = "async-network-client")] + { + gix_protocol::futures_lite::io::BlockOn::new(reader) + }, + #[cfg(not(feature = "async-network-client"))] + { + reader + }, + Some(repo.objects.store_ref().path().join("pack")), + progress, + should_interrupt, + Some(Box::new({ + let repo = repo.clone(); + move |oid, buf| repo.objects.find(oid, buf).ok() + })), + options, + )?) + } else { + drop(reader); + None + }; + + if matches!(protocol_version, gix_protocol::transport::Protocol::V2) { + 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())?; + 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())?; + } + } + write_pack_bundle } - } + }; let update_refs = refs::update( repo, @@ -243,16 +291,26 @@ where } } - Ok(Outcome { + let out = Outcome { ref_map: std::mem::take(&mut self.ref_map), - status: match write_pack_bundle { - Some(write_pack_bundle) => Status::Change { - write_pack_bundle, + status: if matches!(self.dry_run, fetch::DryRun::Yes) { + assert!(write_pack_bundle.is_none(), "in dry run we never read a bundle"); + Status::DryRun { update_refs, - }, - None => Status::DryRun { update_refs }, + negotiation_rounds: round, + } + } else { + match write_pack_bundle { + Some(write_pack_bundle) => Status::Change { + write_pack_bundle, + update_refs, + negotiation_rounds: round, + }, + None => Status::NoPackReceived { update_refs }, + } }, - }) + }; + Ok(out) } } diff --git a/vendor/gix/src/remote/connection/fetch/update_refs/tests.rs b/vendor/gix/src/remote/connection/fetch/update_refs/tests.rs index 145990ac8..47ab5d1a5 100644 --- a/vendor/gix/src/remote/connection/fetch/update_refs/tests.rs +++ b/vendor/gix/src/remote/connection/fetch/update_refs/tests.rs @@ -140,7 +140,7 @@ mod update { &specs, &[], fetch::Tags::None, - reflog_message.map(|_| fetch::DryRun::Yes).unwrap_or(fetch::DryRun::No), + reflog_message.map_or(fetch::DryRun::No, |_| fetch::DryRun::Yes), fetch::WritePackedRefs::Never, ) .unwrap(); @@ -153,7 +153,7 @@ mod update { }], "{spec:?}: {detail}" ); - assert_eq!(out.edits.len(), reflog_message.map(|_| 1).unwrap_or(0)); + assert_eq!(out.edits.len(), reflog_message.map_or(0, |_| 1)); if let Some(reflog_message) = reflog_message { let edit = &out.edits[0]; match &edit.change { @@ -559,13 +559,13 @@ mod update { .mappings .into_iter() .map(|m| fetch::Mapping { - remote: m - .item_index - .map(|idx| fetch::Source::Ref(references[idx].clone())) - .unwrap_or_else(|| match m.lhs { + remote: m.item_index.map_or_else( + || match m.lhs { gix_refspec::match_group::SourceRef::ObjectId(id) => fetch::Source::ObjectId(id), _ => unreachable!("not a ref, must be id: {:?}", m), - }), + }, + |idx| fetch::Source::Ref(references[idx].clone()), + ), local: m.rhs.map(|r| r.into_owned()), spec_index: SpecIndex::ExplicitInRemote(m.spec_index), }) diff --git a/vendor/gix/src/remote/connection/ref_map.rs b/vendor/gix/src/remote/connection/ref_map.rs index abf9c8e00..95ddb6214 100644 --- a/vendor/gix/src/remote/connection/ref_map.rs +++ b/vendor/gix/src/remote/connection/ref_map.rs @@ -148,15 +148,15 @@ where let mappings = mappings .into_iter() .map(|m| fetch::Mapping { - remote: m - .item_index - .map(|idx| fetch::Source::Ref(remote.refs[idx].clone())) - .unwrap_or_else(|| { + remote: m.item_index.map_or_else( + || { fetch::Source::ObjectId(match m.lhs { gix_refspec::match_group::SourceRef::ObjectId(id) => id, _ => unreachable!("no item index implies having an object id"), }) - }), + }, + |idx| fetch::Source::Ref(remote.refs[idx].clone()), + ), local: m.rhs.map(|c| c.into_owned()), spec_index: if m.spec_index < num_explicit_specs { SpecIndex::ExplicitInRemote(m.spec_index) @@ -191,11 +191,10 @@ where let authenticate = match self.authenticate.as_mut() { Some(f) => f, None => { - let url = self - .remote - .url(Direction::Fetch) - .map(ToOwned::to_owned) - .unwrap_or_else(|| gix_url::parse(url.as_ref()).expect("valid URL to be provided by transport")); + let url = self.remote.url(Direction::Fetch).map_or_else( + || gix_url::parse(url.as_ref()).expect("valid URL to be provided by transport"), + ToOwned::to_owned, + ); credentials_storage = self.configured_credentials(url)?; &mut credentials_storage } diff --git a/vendor/gix/src/remote/fetch.rs b/vendor/gix/src/remote/fetch.rs index 0001447cb..0947ace3f 100644 --- a/vendor/gix/src/remote/fetch.rs +++ b/vendor/gix/src/remote/fetch.rs @@ -1,3 +1,18 @@ +/// +pub mod negotiate { + pub use gix_negotiate::Algorithm; + + #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] + pub use super::super::connection::fetch::negotiate::Error; + #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] + pub(crate) use super::super::connection::fetch::negotiate::{ + add_wants, mark_complete_and_common_ref, one_round, Action, + }; +} + +#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] +pub use super::connection::fetch::{prepare, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Status}; + /// If `Yes`, don't really make changes but do as much as possible to get an idea of what would be done. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] @@ -53,6 +68,8 @@ impl Tags { #[derive(Default, Debug, Clone, PartialEq, Eq)] pub enum Shallow { /// Fetch all changes from the remote without affecting the shallow boundary at all. + /// + /// This also means that repositories that aren't shallow will remain like that. #[default] NoChange, /// Receive update to `depth` commits in the history of the refs to fetch (from the viewpoint of the remote), @@ -126,7 +143,7 @@ pub enum Source { #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] impl Source { /// Return either the direct object id we refer to or the direct target that a reference refers to. - /// The latter may be a direct or a symbolic reference, and we degenerate this to the peeled object id. + /// The latter may be a direct or a symbolic reference. /// If unborn, `None` is returned. pub fn as_id(&self) -> Option<&gix_hash::oid> { match self { @@ -135,6 +152,17 @@ impl Source { } } + /// Returns the peeled id of this instance, that is the object that can't be de-referenced anymore. + pub fn peeled_id(&self) -> Option<&gix_hash::oid> { + match self { + Source::ObjectId(id) => Some(id), + Source::Ref(r) => { + let (_name, target, peeled) = r.unpack(); + peeled.or(target) + } + } + } + /// Return ourselves as the full name of the reference we represent, or `None` if this source isn't a reference but an object. pub fn as_name(&self) -> Option<&crate::bstr::BStr> { match self { @@ -195,8 +223,3 @@ pub struct Mapping { /// The index into the fetch ref-specs used to produce the mapping, allowing it to be recovered. pub spec_index: SpecIndex, } - -#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] -pub use super::connection::fetch::{ - negotiate, prepare, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Status, -}; diff --git a/vendor/gix/src/repository/config/mod.rs b/vendor/gix/src/repository/config/mod.rs index 92b2618cc..e5c8b64f3 100644 --- a/vendor/gix/src/repository/config/mod.rs +++ b/vendor/gix/src/repository/config/mod.rs @@ -155,7 +155,7 @@ mod branch { /// In some cases, the returned name will be an URL. /// Returns `None` if the remote was not found or if the name contained illformed UTF-8. /// - /// See also [Reference::remote_name()][crate::Reference::remote_name()] for a more typesafe version + /// See also [`Reference::remote_name()`][crate::Reference::remote_name()] for a more typesafe version /// to be used when a `Reference` is available. pub fn branch_remote_name<'a>( &self, diff --git a/vendor/gix/src/repository/graph.rs b/vendor/gix/src/repository/graph.rs new file mode 100644 index 000000000..a1f6c7f89 --- /dev/null +++ b/vendor/gix/src/repository/graph.rs @@ -0,0 +1,24 @@ +use gix_odb::Find; + +impl crate::Repository { + /// Create a graph data-structure capable of accelerating graph traversals and storing state of type `T` with each commit + /// it encountered. + /// + /// Note that the commitgraph will be used if it is present and readable, but it won't be an error if it is corrupted. In that case, + /// it will just not be used. + /// + /// ### Performance + /// + /// Note that the [Graph][gix_revision::Graph] can be sensitive to various object database settings that may affect the performance + /// of the commit walk. + pub fn commit_graph<T>(&self) -> gix_revision::Graph<'_, T> { + gix_revision::Graph::new( + |id, buf| { + self.objects + .try_find(id, buf) + .map(|r| r.and_then(|d| d.try_into_commit_iter())) + }, + gix_commitgraph::at(self.objects.store_ref().path().join("info")).ok(), + ) + } +} diff --git a/vendor/gix/src/repository/init.rs b/vendor/gix/src/repository/init.rs index 16659a013..255ff90d6 100644 --- a/vendor/gix/src/repository/init.rs +++ b/vendor/gix/src/repository/init.rs @@ -37,7 +37,12 @@ fn setup_objects(mut objects: crate::OdbHandle, config: &crate::config::Cache) - #[cfg(feature = "max-performance-safe")] { match config.pack_cache_bytes { - None => objects.set_pack_cache(|| Box::<gix_pack::cache::lru::StaticLinkedList<64>>::default()), + None => match config.static_pack_cache_limit_bytes { + None => objects.set_pack_cache(|| Box::<gix_pack::cache::lru::StaticLinkedList<64>>::default()), + Some(limit) => { + objects.set_pack_cache(move || Box::new(gix_pack::cache::lru::StaticLinkedList::<64>::new(limit))) + } + }, Some(0) => objects.unset_pack_cache(), Some(bytes) => objects.set_pack_cache(move || -> Box<gix_odb::cache::PackCache> { Box::new(gix_pack::cache::lru::MemoryCappedHashmap::new(bytes)) diff --git a/vendor/gix/src/kind.rs b/vendor/gix/src/repository/kind.rs index a8213475f..88779e0cc 100644 --- a/vendor/gix/src/kind.rs +++ b/vendor/gix/src/repository/kind.rs @@ -1,4 +1,4 @@ -use crate::Kind; +use crate::repository::Kind; impl Kind { /// Returns true if this is a bare repository, one without a work tree. diff --git a/vendor/gix/src/repository/location.rs b/vendor/gix/src/repository/location.rs index 0bb8ea253..3e2ff907c 100644 --- a/vendor/gix/src/repository/location.rs +++ b/vendor/gix/src/repository/location.rs @@ -69,18 +69,18 @@ impl crate::Repository { } /// Return the kind of repository, either bare or one with a work tree. - pub fn kind(&self) -> crate::Kind { + pub fn kind(&self) -> crate::repository::Kind { match self.worktree() { Some(wt) => { if gix_discover::is_submodule_git_dir(self.git_dir()) { - crate::Kind::Submodule + crate::repository::Kind::Submodule } else { - crate::Kind::WorkTree { + crate::repository::Kind::WorkTree { is_linked: !wt.is_main(), } } } - None => crate::Kind::Bare, + None => crate::repository::Kind::Bare, } } } diff --git a/vendor/gix/src/repository/mod.rs b/vendor/gix/src/repository/mod.rs index 5b7a70d3b..f8a51e8d0 100644 --- a/vendor/gix/src/repository/mod.rs +++ b/vendor/gix/src/repository/mod.rs @@ -1,5 +1,19 @@ //! +/// The kind of repository. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum Kind { + /// A submodule worktree, whose `git` repository lives in `.git/modules/**/<name>` of the parent repository. + Submodule, + /// A bare repository does not have a work tree, that is files on disk beyond the `git` repository itself. + Bare, + /// A `git` repository along with a checked out files in a work tree. + WorkTree { + /// If true, this is the git dir associated with this _linked_ worktree, otherwise it is a repository with _main_ worktree. + is_linked: bool, + }, +} + /// Internal impl crate::Repository { #[inline] @@ -23,9 +37,11 @@ mod attributes; mod cache; mod config; mod excludes; +mod graph; pub(crate) mod identity; mod impls; mod init; +mod kind; mod location; mod object; mod reference; diff --git a/vendor/gix/src/repository/object.rs b/vendor/gix/src/repository/object.rs index f4592475f..787dcda4e 100644 --- a/vendor/gix/src/repository/object.rs +++ b/vendor/gix/src/repository/object.rs @@ -1,6 +1,5 @@ #![allow(clippy::result_large_err)] -use std::convert::TryInto; -use std::ops::DerefMut; +use std::{convert::TryInto, ops::DerefMut}; use gix_hash::ObjectId; use gix_odb::{Find, FindExt, Write}; diff --git a/vendor/gix/src/repository/remote.rs b/vendor/gix/src/repository/remote.rs index e3f210899..74ebbaea0 100644 --- a/vendor/gix/src/repository/remote.rs +++ b/vendor/gix/src/repository/remote.rs @@ -42,7 +42,7 @@ impl crate::Repository { /// Find the default remote as configured, or `None` if no such configuration could be found. /// - /// See [remote_default_name()][Self::remote_default_name()] for more information on the `direction` parameter. + /// See [`remote_default_name()`][Self::remote_default_name()] for more information on the `direction` parameter. pub fn find_default_remote( &self, direction: remote::Direction, @@ -65,7 +65,7 @@ impl crate::Repository { self.try_find_remote_inner(name_or_url, true) } - /// Similar to [try_find_remote()][Self::try_find_remote()], but removes a failure mode if rewritten URLs turn out to be invalid + /// Similar to [`try_find_remote()`][Self::try_find_remote()], but removes a failure mode if rewritten URLs turn out to be invalid /// as it skips rewriting them. /// Use this in conjunction with [`Remote::rewrite_urls()`] to non-destructively apply the rules and keep the failed urls unchanged. pub fn try_find_remote_without_url_rewrite<'a>( diff --git a/vendor/gix/src/repository/snapshots.rs b/vendor/gix/src/repository/snapshots.rs index 6933dc9c6..96de5080d 100644 --- a/vendor/gix/src/repository/snapshots.rs +++ b/vendor/gix/src/repository/snapshots.rs @@ -104,6 +104,6 @@ impl crate::Repository { target.merge(gix_mailmap::parse_ignore_errors(&buf)); } - err.map(Err).unwrap_or(Ok(())) + err.map_or(Ok(()), Err) } } diff --git a/vendor/gix/src/repository/thread_safe.rs b/vendor/gix/src/repository/thread_safe.rs index 7c89aee60..ea7cedf9d 100644 --- a/vendor/gix/src/repository/thread_safe.rs +++ b/vendor/gix/src/repository/thread_safe.rs @@ -1,17 +1,5 @@ mod access { - use crate::Kind; - impl crate::ThreadSafeRepository { - /// Return the kind of repository, either bare or one with a work tree. - pub fn kind(&self) -> Kind { - match self.work_tree { - Some(_) => Kind::WorkTree { - is_linked: crate::worktree::id(self.git_dir(), self.common_dir.is_some()).is_some(), - }, - None => Kind::Bare, - } - } - /// Add thread-local state to an easy-to-use thread-local repository for the most convenient API. pub fn to_thread_local(&self) -> crate::Repository { self.into() diff --git a/vendor/gix/src/repository/worktree.rs b/vendor/gix/src/repository/worktree.rs index 316009d29..f522a3f18 100644 --- a/vendor/gix/src/repository/worktree.rs +++ b/vendor/gix/src/repository/worktree.rs @@ -1,5 +1,4 @@ -use crate::config::cache::util::ApplyLeniencyDefault; -use crate::{worktree, Worktree}; +use crate::{config::cache::util::ApplyLeniencyDefault, worktree, Worktree}; /// Interact with individual worktrees and their information. impl crate::Repository { diff --git a/vendor/gix/src/revision/spec/parse/delegate/navigate.rs b/vendor/gix/src/revision/spec/parse/delegate/navigate.rs index 882c2835c..f6e085368 100644 --- a/vendor/gix/src/revision/spec/parse/delegate/navigate.rs +++ b/vendor/gix/src/revision/spec/parse/delegate/navigate.rs @@ -239,8 +239,7 @@ impl<'repo> delegate::Navigate for Delegate<'repo> { r.id() .object() .ok() - .map(|obj| obj.kind == gix_object::Kind::Commit) - .unwrap_or(false) + .map_or(false, |obj| obj.kind == gix_object::Kind::Commit) }) .filter_map(|r| r.detach().peeled), ) diff --git a/vendor/gix/src/types.rs b/vendor/gix/src/types.rs index eafa6a3f8..cdb5f731d 100644 --- a/vendor/gix/src/types.rs +++ b/vendor/gix/src/types.rs @@ -4,20 +4,6 @@ use gix_hash::ObjectId; use crate::{head, remote}; -/// The kind of repository. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum Kind { - /// A submodule worktree, whose `git` repository lives in `.git/modules/**/<name>` of the parent repository. - Submodule, - /// A bare repository does not have a work tree, that is files on disk beyond the `git` repository itself. - Bare, - /// A `git` repository along with a checked out files in a work tree. - WorkTree { - /// If true, this is the git dir associated with this _linked_ worktree, otherwise it is a repository with _main_ worktree. - is_linked: bool, - }, -} - /// A worktree checkout containing the files of the repository in consumable form. pub struct Worktree<'repo> { pub(crate) parent: &'repo Repository, @@ -35,7 +21,7 @@ pub struct Head<'repo> { pub(crate) repo: &'repo Repository, } -/// An [ObjectId] with access to a repository. +/// An [`ObjectId`] with access to a repository. #[derive(Clone, Copy)] pub struct Id<'r> { /// The actual object id |