diff options
Diffstat (limited to 'vendor/gix/src')
67 files changed, 1339 insertions, 1067 deletions
diff --git a/vendor/gix/src/attribute_stack.rs b/vendor/gix/src/attribute_stack.rs index 1aaca0f2b..66497def6 100644 --- a/vendor/gix/src/attribute_stack.rs +++ b/vendor/gix/src/attribute_stack.rs @@ -1,9 +1,7 @@ -use crate::bstr::BStr; -use crate::types::AttributeStack; -use crate::Repository; -use gix_odb::FindExt; use std::ops::{Deref, DerefMut}; +use crate::{bstr::BStr, types::AttributeStack, Repository}; + /// Lifecycle impl<'repo> AttributeStack<'repo> { /// Create a new instance from a `repo` and the underlying pre-configured `stack`. @@ -46,8 +44,7 @@ impl<'repo> AttributeStack<'repo> { relative: impl AsRef<std::path::Path>, is_dir: Option<bool>, ) -> std::io::Result<gix_worktree::stack::Platform<'_>> { - self.inner - .at_path(relative, is_dir, |id, buf| self.repo.objects.find_blob(id, buf)) + self.inner.at_path(relative, is_dir, &self.repo.objects) } /// Obtain a platform for lookups from a repo-`relative` path, typically obtained from an index entry. `is_dir` should reflect @@ -63,7 +60,6 @@ impl<'repo> AttributeStack<'repo> { relative: impl Into<&'r BStr>, is_dir: Option<bool>, ) -> std::io::Result<gix_worktree::stack::Platform<'_>> { - self.inner - .at_entry(relative, is_dir, |id, buf| self.repo.objects.find_blob(id, buf)) + self.inner.at_entry(relative, is_dir, &self.repo.objects) } } diff --git a/vendor/gix/src/clone/checkout.rs b/vendor/gix/src/clone/checkout.rs index ece480a56..84a3fedbd 100644 --- a/vendor/gix/src/clone/checkout.rs +++ b/vendor/gix/src/clone/checkout.rs @@ -4,8 +4,6 @@ use crate::{clone::PrepareCheckout, Repository}; pub mod main_worktree { use std::{path::PathBuf, sync::atomic::AtomicBool}; - use gix_odb::FindExt; - use crate::{clone::PrepareCheckout, Progress, Repository}; /// The error returned by [`PrepareCheckout::main_worktree()`]. @@ -26,7 +24,7 @@ pub mod main_worktree { #[error(transparent)] CheckoutOptions(#[from] crate::config::checkout_options::Error), #[error(transparent)] - IndexCheckout(#[from] gix_worktree_state::checkout::Error<gix_odb::find::existing_object::Error>), + IndexCheckout(#[from] gix_worktree_state::checkout::Error), #[error("Failed to reopen object database as Arc (only if thread-safety wasn't compiled in)")] OpenArcOdb(#[from] std::io::Error), #[error("The HEAD reference could not be located")] @@ -87,7 +85,7 @@ pub mod main_worktree { let workdir = repo.work_dir().ok_or_else(|| Error::BareRepository { git_dir: repo.git_dir().to_owned(), })?; - let root_tree = match repo.head()?.peel_to_id_in_place().transpose()? { + let root_tree = match repo.head()?.try_peel_to_id_in_place()? { Some(id) => id.object().expect("downloaded from remote").peel_to_tree()?.id, None => { return Ok(( @@ -96,11 +94,10 @@ pub mod main_worktree { )) } }; - let index = gix_index::State::from_tree(&root_tree, |oid, buf| repo.objects.find_tree_iter(oid, buf).ok()) - .map_err(|err| Error::IndexFromTree { - id: root_tree, - source: err, - })?; + let index = gix_index::State::from_tree(&root_tree, &repo.objects).map_err(|err| Error::IndexFromTree { + id: root_tree, + source: err, + })?; let mut index = gix_index::File::from_state(index, repo.index_path()); let mut opts = repo @@ -118,10 +115,7 @@ pub mod main_worktree { let outcome = gix_worktree_state::checkout( &mut index, workdir, - { - let objects = repo.objects.clone().into_arc()?; - move |oid, buf| objects.find_blob(oid, buf) - }, + repo.objects.clone().into_arc()?, &files, &bytes, should_interrupt, diff --git a/vendor/gix/src/clone/fetch/util.rs b/vendor/gix/src/clone/fetch/util.rs index db9bc0a1c..627201301 100644 --- a/vendor/gix/src/clone/fetch/util.rs +++ b/vendor/gix/src/clone/fetch/util.rs @@ -1,6 +1,5 @@ use std::{borrow::Cow, convert::TryInto, io::Write}; -use gix_odb::Find; use gix_ref::{ transaction::{LogChange, RefLog}, FullNameRef, @@ -107,7 +106,7 @@ pub fn update_head( repo.refs .transaction() .packed_refs(gix_ref::file::transaction::PackedRefs::DeletionsAndNonSymbolicUpdates( - Box::new(|oid, buf| repo.objects.try_find(&oid, buf).map(|obj| obj.map(|obj| obj.kind))), + Box::new(&repo.objects), )) .prepare( { diff --git a/vendor/gix/src/commit.rs b/vendor/gix/src/commit.rs index 2cc8226f5..ce5dee4d6 100644 --- a/vendor/gix/src/commit.rs +++ b/vendor/gix/src/commit.rs @@ -28,7 +28,6 @@ pub mod describe { use gix_hash::ObjectId; use gix_hashtable::HashMap; - use gix_odb::Find; use crate::{bstr::BStr, ext::ObjectIdExt, Repository}; @@ -199,12 +198,7 @@ pub mod describe { pub fn try_resolve(&self) -> Result<Option<Resolution<'repo>>, Error> { // TODO: dirty suffix with respective dirty-detection let mut graph = gix_revwalk::Graph::new( - |id, buf| { - self.repo - .objects - .try_find(id, buf) - .map(|r| r.and_then(gix_object::Data::try_into_commit_iter)) - }, + &self.repo.objects, gix_commitgraph::Graph::from_info_dir(self.repo.objects.store_ref().path().join("info").as_ref()).ok(), ); let outcome = gix_revision::describe( diff --git a/vendor/gix/src/config/cache/access.rs b/vendor/gix/src/config/cache/access.rs index 3e763c028..464a0bf4d 100644 --- a/vendor/gix/src/config/cache/access.rs +++ b/vendor/gix/src/config/cache/access.rs @@ -20,8 +20,7 @@ use crate::{ impl Cache { #[cfg(feature = "blob-diff")] pub(crate) fn diff_algorithm(&self) -> Result<gix_diff::blob::Algorithm, config::diff::algorithm::Error> { - use crate::config::cache::util::ApplyLeniencyDefault; - use crate::config::diff::algorithm::Error; + use crate::config::{cache::util::ApplyLeniencyDefault, diff::algorithm::Error}; self.diff_algorithm .get_or_try_init(|| { let name = self @@ -39,6 +38,97 @@ impl Cache { .copied() } + #[cfg(feature = "blob-diff")] + pub(crate) fn diff_drivers(&self) -> Result<Vec<gix_diff::blob::Driver>, config::diff::drivers::Error> { + use crate::config::cache::util::ApplyLeniencyDefault; + let mut out = Vec::<gix_diff::blob::Driver>::new(); + for section in self + .resolved + .sections_by_name("diff") + .into_iter() + .flatten() + .filter(|s| (self.filter_config_section)(s.meta())) + { + let Some(name) = section.header().subsection_name().filter(|n| !n.is_empty()) else { + continue; + }; + + let driver = match out.iter_mut().find(|d| d.name == name) { + Some(existing) => existing, + None => { + out.push(gix_diff::blob::Driver { + name: name.into(), + ..Default::default() + }); + out.last_mut().expect("just pushed") + } + }; + + if let Some(binary) = section.value_implicit("binary") { + driver.is_binary = config::tree::Diff::DRIVER_BINARY + .try_into_binary(binary) + .with_leniency(self.lenient_config) + .map_err(|err| config::diff::drivers::Error { + name: driver.name.clone(), + attribute: "binary", + source: Box::new(err), + })?; + } + if let Some(command) = section.value(config::tree::Diff::DRIVER_COMMAND.name) { + driver.command = command.into_owned().into(); + } + if let Some(textconv) = section.value(config::tree::Diff::DRIVER_TEXTCONV.name) { + driver.binary_to_text_command = textconv.into_owned().into(); + } + if let Some(algorithm) = section.value("algorithm") { + driver.algorithm = config::tree::Diff::DRIVER_ALGORITHM + .try_into_algorithm(algorithm) + .or_else(|err| match err { + config::diff::algorithm::Error::Unimplemented { .. } if self.lenient_config => { + Ok(gix_diff::blob::Algorithm::Histogram) + } + err => Err(err), + }) + .with_lenient_default(self.lenient_config) + .map_err(|err| config::diff::drivers::Error { + name: driver.name.clone(), + attribute: "algorithm", + source: Box::new(err), + })? + .into(); + } + } + Ok(out) + } + + #[cfg(feature = "blob-diff")] + pub(crate) fn diff_pipeline_options( + &self, + ) -> Result<gix_diff::blob::pipeline::Options, config::diff::pipeline_options::Error> { + Ok(gix_diff::blob::pipeline::Options { + large_file_threshold_bytes: self.big_file_threshold()?, + fs: self.fs_capabilities()?, + }) + } + + #[cfg(feature = "blob-diff")] + pub(crate) fn diff_renames(&self) -> Result<Option<crate::diff::Rewrites>, crate::diff::new_rewrites::Error> { + self.diff_renames + .get_or_try_init(|| crate::diff::new_rewrites(&self.resolved, self.lenient_config)) + .copied() + } + + #[cfg(feature = "blob-diff")] + pub(crate) fn big_file_threshold(&self) -> Result<u64, config::unsigned_integer::Error> { + Ok(self + .resolved + .integer_by_key("core.bigFileThreshold") + .map(|number| Core::BIG_FILE_THRESHOLD.try_into_u64(number)) + .transpose() + .with_leniency(self.lenient_config)? + .unwrap_or(512 * 1024 * 1024)) + } + /// Returns a user agent for use with servers. #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] pub(crate) fn user_agent_tuple(&self) -> (&'static str, Option<Cow<'static, str>>) { @@ -54,6 +144,18 @@ impl Cache { ("agent", Some(gix_protocol::agent(agent).into())) } + /// Return `true` if packet-tracing is enabled. Lenient and defaults to `false`. + #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] + pub(crate) fn trace_packet(&self) -> bool { + use config::tree::Gitoxide; + + use crate::config::tree::Section; + self.resolved + .boolean(Gitoxide.name(), None, Gitoxide::TRACE_PACKET.name()) + .and_then(Result::ok) + .unwrap_or_default() + } + pub(crate) fn personas(&self) -> &identity::Personas { self.personas .get_or_init(|| identity::Personas::from_config_and_env(&self.resolved)) @@ -81,17 +183,6 @@ impl Cache { }) } - #[cfg(feature = "blob-diff")] - pub(crate) fn diff_renames( - &self, - ) -> Result<Option<crate::object::tree::diff::Rewrites>, crate::object::tree::diff::rewrites::Error> { - self.diff_renames - .get_or_try_init(|| { - crate::object::tree::diff::Rewrites::try_from_config(&self.resolved, self.lenient_config) - }) - .copied() - } - /// Returns (file-timeout, pack-refs timeout) pub(crate) fn lock_timeout( &self, @@ -189,8 +280,8 @@ impl Cache { )?; let capabilities = self.fs_capabilities()?; let filters = { - let collection = Default::default(); - let mut filters = gix_filter::Pipeline::new(&collection, crate::filter::Pipeline::options(repo)?); + let mut filters = + gix_filter::Pipeline::new(repo.command_context()?, crate::filter::Pipeline::options(repo)?); if let Ok(mut head) = repo.head() { let ctx = filters.driver_context_mut(); ctx.ref_name = head.referent_name().map(|name| name.as_bstr().to_owned()); diff --git a/vendor/gix/src/config/cache/init.rs b/vendor/gix/src/config/cache/init.rs index 3c482b154..faf3cc8de 100644 --- a/vendor/gix/src/config/cache/init.rs +++ b/vendor/gix/src/config/cache/init.rs @@ -9,7 +9,7 @@ use crate::{ config, config::{ cache::util::ApplyLeniency, - tree::{gitoxide, Core, Http}, + tree::{gitoxide, Core, Gitoxide, Http}, Cache, }, open, @@ -143,6 +143,7 @@ impl Cache { use util::config_bool; let reflog = util::query_refupdates(&config, lenient_config)?; + let refs_namespace = util::query_refs_namespace(&config, lenient_config)?; let ignore_case = config_bool(&config, &Core::IGNORE_CASE, "core.ignoreCase", false, lenient_config)?; let use_multi_pack_index = config_bool( &config, @@ -166,6 +167,7 @@ impl Cache { pack_cache_bytes, object_cache_bytes, reflog, + refs_namespace, is_bare, ignore_case, hex_len, @@ -222,10 +224,12 @@ impl Cache { self.object_kind_hint = object_kind_hint; } let reflog = util::query_refupdates(config, self.lenient_config)?; + let refs_namespace = util::query_refs_namespace(config, self.lenient_config)?; self.hex_len = hex_len; self.ignore_case = ignore_case; self.reflog = reflog; + self.refs_namespace = refs_namespace; self.user_agent = Default::default(); self.personas = Default::default(); @@ -298,6 +302,7 @@ impl crate::Repository { fn apply_changed_values(&mut self) { self.refs.write_reflog = util::reflog_or_default(self.config.reflog, self.work_dir().is_some()); + self.refs.namespace = self.config.refs_namespace.clone(); } } @@ -339,6 +344,15 @@ fn apply_environment_overrides( ), ( "gitoxide", + None, + git_prefix, + &[{ + let key = &Gitoxide::TRACE_PACKET; + (env(key), key.name) + }], + ), + ( + "gitoxide", Some(Cow::Borrowed("https".into())), http_transport, &[ @@ -377,6 +391,30 @@ fn apply_environment_overrides( ), ( "gitoxide", + Some(Cow::Borrowed("http".into())), + git_prefix, + &[{ + let key = &gitoxide::Http::SSL_NO_VERIFY; + (env(key), key.name) + }], + ), + ( + "gitoxide", + Some(Cow::Borrowed("credentials".into())), + git_prefix, + &[ + { + let key = &gitoxide::Credentials::TERMINAL_PROMPT; + (env(key), key.name) + }, + { + let key = &gitoxide::Credentials::HELPER_STDERR; + (env(key), key.name) + }, + ], + ), + ( + "gitoxide", Some(Cow::Borrowed("committer".into())), identity, &[ @@ -394,10 +432,20 @@ fn apply_environment_overrides( "gitoxide", Some(Cow::Borrowed("core".into())), git_prefix, - &[{ - let key = &gitoxide::Core::SHALLOW_FILE; - (env(key), key.name) - }], + &[ + { + let key = &gitoxide::Core::SHALLOW_FILE; + (env(key), key.name) + }, + { + let key = &gitoxide::Core::REFS_NAMESPACE; + (env(key), key.name) + }, + { + let key = &gitoxide::Core::EXTERNAL_COMMAND_STDERR; + (env(key), key.name) + }, + ], ), ( "gitoxide", @@ -500,6 +548,16 @@ fn apply_environment_overrides( (env(key), key.name) }], ), + #[cfg(feature = "blob-diff")] + ( + "diff", + None, + git_prefix, + &[{ + let key = &config::tree::Diff::EXTERNAL; + (env(key), key.name) + }], + ), ] { let mut section = env_override .new_section(section_name, subsection_name) diff --git a/vendor/gix/src/config/cache/util.rs b/vendor/gix/src/config/cache/util.rs index 4032b2cb1..4c1d6c693 100644 --- a/vendor/gix/src/config/cache/util.rs +++ b/vendor/gix/src/config/cache/util.rs @@ -55,6 +55,18 @@ pub(crate) fn query_refupdates( .map_err(Into::into) } +pub(crate) fn query_refs_namespace( + config: &gix_config::File<'static>, + lenient_config: bool, +) -> Result<Option<gix_ref::Namespace>, config::refs_namespace::Error> { + let key = "gitoxide.core.refsNamespace"; + config + .string_by_key(key) + .map(|ns| gitoxide::Core::REFS_NAMESPACE.try_into_refs_namespace(ns)) + .transpose() + .with_leniency(lenient_config) +} + pub(crate) fn reflog_or_default( config_reflog: Option<gix_ref::store::WriteReflog>, has_worktree: bool, diff --git a/vendor/gix/src/config/mod.rs b/vendor/gix/src/config/mod.rs index 102c7a482..301e19ba2 100644 --- a/vendor/gix/src/config/mod.rs +++ b/vendor/gix/src/config/mod.rs @@ -76,6 +76,8 @@ pub enum Error { ConfigUnsigned(#[from] unsigned_integer::Error), #[error(transparent)] ConfigTypedString(#[from] key::GenericErrorWithValue), + #[error(transparent)] + RefsNamespace(#[from] refs_namespace::Error), #[error("Cannot handle objects formatted as {:?}", .name)] UnsupportedObjectFormat { name: BString }, #[error(transparent)] @@ -91,8 +93,11 @@ pub enum Error { ResolveIncludes(#[from] gix_config::file::includes::Error), #[error(transparent)] FromEnv(#[from] gix_config::file::init::from_env::Error), - #[error(transparent)] - PathInterpolation(#[from] gix_config::path::interpolate::Error), + #[error("The path {path:?} at the 'core.worktree' configuration could not be interpolated")] + PathInterpolation { + path: BString, + source: gix_config::path::interpolate::Error, + }, #[error("{source:?} configuration overrides at open or init time could not be applied.")] ConfigOverrides { #[source] @@ -117,6 +122,36 @@ pub mod diff { Unimplemented { name: BString }, } } + + /// + pub mod pipeline_options { + /// The error produced when obtaining options needed to fill in [gix_diff::blob::pipeline::Options]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + FilesystemCapabilities(#[from] crate::config::boolean::Error), + #[error(transparent)] + BigFileThreshold(#[from] crate::config::unsigned_integer::Error), + } + } + + /// + pub mod drivers { + use crate::bstr::BString; + + /// The error produced when obtaining a list of [Drivers](gix_diff::blob::Driver). + #[derive(Debug, thiserror::Error)] + #[error("Failed to parse value of 'diff.{name}.{attribute}'")] + pub struct Error { + /// The name fo the driver. + pub name: BString, + /// The name of the attribute we tried to parse. + pub attribute: &'static str, + /// The actual error that occurred. + pub source: Box<dyn std::error::Error + Send + Sync + 'static>, + } + } } /// @@ -149,6 +184,25 @@ pub mod checkout_options { Attributes(#[from] super::attribute_stack::Error), #[error(transparent)] FilterPipelineOptions(#[from] crate::filter::pipeline::options::Error), + #[error(transparent)] + CommandContext(#[from] crate::config::command_context::Error), + } +} + +/// +#[cfg(feature = "attributes")] +pub mod command_context { + use crate::config; + + /// The error produced when collecting all information relevant to spawned commands, + /// obtained via [Repository::command_context()](crate::Repository::command_context()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Boolean(#[from] config::boolean::Error), + #[error(transparent)] + ParseBool(#[from] gix_config::value::Error), } } @@ -409,6 +463,12 @@ pub mod refspec { } /// +pub mod refs_namespace { + /// The error produced when failing to parse a refspec from the configuration. + pub type Error = super::key::Error<gix_validate::reference::name::Error, 'v', 'i'>; +} + +/// pub mod ssl_version { /// The error produced when failing to parse a refspec from the configuration. pub type Error = super::key::Error<std::convert::Infallible, 's', 'i'>; @@ -504,6 +564,8 @@ pub(crate) struct Cache { pub use_multi_pack_index: bool, /// The representation of `core.logallrefupdates`, or `None` if the variable wasn't set. pub reflog: Option<gix_ref::store::WriteReflog>, + /// The representation of `gitoxide.core.refsNamespace`, or `None` if the variable wasn't set. + pub refs_namespace: Option<gix_ref::Namespace>, /// The configured user agent for presentation to servers. pub(crate) user_agent: OnceCell<String>, /// identities for later use, lazy initialization. @@ -512,7 +574,7 @@ pub(crate) struct Cache { pub(crate) url_rewrite: OnceCell<crate::remote::url::Rewrite>, /// The lazy-loaded rename information for diffs. #[cfg(feature = "blob-diff")] - pub(crate) diff_renames: OnceCell<Option<crate::object::tree::diff::Rewrites>>, + pub(crate) diff_renames: OnceCell<Option<crate::diff::Rewrites>>, /// A lazily loaded mapping to know which url schemes to allow #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] pub(crate) url_scheme: OnceCell<crate::remote::url::SchemePermission>, @@ -541,3 +603,23 @@ pub(crate) struct Cache { environment: crate::open::permissions::Environment, // TODO: make core.precomposeUnicode available as well. } + +/// Utillities shared privately across the crate, for lack of a better place. +pub(crate) mod shared { + use crate::{ + config, + config::{cache::util::ApplyLeniency, tree::Core}, + }; + + pub fn is_replace_refs_enabled( + config: &gix_config::File<'static>, + lenient: bool, + mut filter_config_section: fn(&gix_config::file::Metadata) -> bool, + ) -> Result<Option<bool>, config::boolean::Error> { + config + .boolean_filter_by_key("core.useReplaceRefs", &mut filter_config_section) + .map(|b| Core::USE_REPLACE_REFS.enrich_error(b)) + .transpose() + .with_leniency(lenient) + } +} diff --git a/vendor/gix/src/config/snapshot/access.rs b/vendor/gix/src/config/snapshot/access.rs index 7dc593880..6b98cf42d 100644 --- a/vendor/gix/src/config/snapshot/access.rs +++ b/vendor/gix/src/config/snapshot/access.rs @@ -4,9 +4,8 @@ use std::borrow::Cow; use gix_features::threading::OwnShared; use gix_macros::momo; -use crate::bstr::ByteSlice; use crate::{ - bstr::{BStr, BString}, + bstr::{BStr, BString, ByteSlice}, config::{CommitAutoRollback, Snapshot, SnapshotMut}, }; @@ -118,9 +117,13 @@ impl<'repo> SnapshotMut<'repo> { } let value = new_value.into(); key.validate(value)?; - let current = self - .config - .set_raw_value(key.section().name(), None, key.name(), value)?; + let section = key.section(); + let current = match section.parent() { + Some(parent) => self + .config + .set_raw_value(parent.name(), Some(section.name().into()), key.name(), value)?, + None => self.config.set_raw_value(section.name(), None, key.name(), value)?, + }; Ok(current.map(std::borrow::Cow::into_owned)) } diff --git a/vendor/gix/src/config/snapshot/credential_helpers.rs b/vendor/gix/src/config/snapshot/credential_helpers.rs index 189e74471..54499a1c3 100644 --- a/vendor/gix/src/config/snapshot/credential_helpers.rs +++ b/vendor/gix/src/config/snapshot/credential_helpers.rs @@ -2,11 +2,12 @@ use std::{borrow::Cow, convert::TryFrom}; pub use error::Error; -use crate::config::cache::util::IgnoreEmptyPath; +use crate::config::cache::util::ApplyLeniency; use crate::{ bstr::{ByteSlice, ByteVec}, config::{ - tree::{credential, Core, Credential, Key}, + cache::util::IgnoreEmptyPath, + tree::{credential, gitoxide::Credentials, Core, Credential, Key}, Snapshot, }, }; @@ -25,6 +26,8 @@ mod error { }, #[error("core.askpass could not be read")] CoreAskpass(#[from] gix_config::path::interpolate::Error), + #[error(transparent)] + BooleanConfig(#[from] crate::config::boolean::Error), } } @@ -144,16 +147,27 @@ impl Snapshot<'_> { .transpose() .ignore_empty()? .map(|c| Cow::Owned(c.into_owned())), - ..Default::default() + mode: self + .try_boolean(Credentials::TERMINAL_PROMPT.logical_name().as_str()) + .map(|val| Credentials::TERMINAL_PROMPT.enrich_error(val)) + .transpose() + .with_leniency(self.repo.config.lenient_config)? + .and_then(|val| (!val).then_some(gix_prompt::Mode::Disable)) + .unwrap_or_default(), } - .apply_environment(allow_git_env, allow_ssh_env, allow_git_env); + .apply_environment(allow_git_env, allow_ssh_env, false /* terminal prompt */); Ok(( gix_credentials::helper::Cascade { programs, use_http_path, // The default ssh implementation uses binaries that do their own auth, so our passwords aren't used. query_user_only: url.scheme == gix_url::Scheme::Ssh, - ..Default::default() + stderr: self + .try_boolean(Credentials::HELPER_STDERR.logical_name().as_str()) + .map(|val| Credentials::HELPER_STDERR.enrich_error(val)) + .transpose() + .with_leniency(self.repo.options.lenient_config)? + .unwrap_or(true), }, gix_credentials::helper::Action::get_for_url(url.to_bstring()), prompt_options, diff --git a/vendor/gix/src/config/tree/sections/core.rs b/vendor/gix/src/config/tree/sections/core.rs index 2ec5c279e..15ad9f947 100644 --- a/vendor/gix/src/config/tree/sections/core.rs +++ b/vendor/gix/src/config/tree/sections/core.rs @@ -8,13 +8,16 @@ impl Core { pub const ABBREV: Abbrev = Abbrev::new_with_validate("abbrev", &config::Tree::CORE, validate::Abbrev); /// The `core.bare` key. pub const BARE: keys::Boolean = keys::Boolean::new_boolean("bare", &config::Tree::CORE); + /// The `core.bigFileThreshold` key. + pub const BIG_FILE_THRESHOLD: keys::UnsignedInteger = + keys::UnsignedInteger::new_unsigned_integer("bigFileThreshold", &config::Tree::CORE); /// The `core.checkStat` key. pub const CHECK_STAT: CheckStat = CheckStat::new_with_validate("checkStat", &config::Tree::CORE, validate::CheckStat); /// The `core.deltaBaseCacheLimit` key. pub const DELTA_BASE_CACHE_LIMIT: keys::UnsignedInteger = keys::UnsignedInteger::new_unsigned_integer("deltaBaseCacheLimit", &config::Tree::CORE) - .with_environment_override("GITOXIDE_PACK_CACHE_MEMORY") + .with_environment_override("GIX_PACK_CACHE_MEMORY") .with_note("if unset, we default to a small 64 slot fixed-size cache that holds at most 64 full delta base objects of any size. Set to 0 to deactivate it entirely"); /// The `core.disambiguate` key. pub const DISAMBIGUATE: Disambiguate = @@ -95,6 +98,7 @@ impl Section for Core { &[ &Self::ABBREV, &Self::BARE, + &Self::BIG_FILE_THRESHOLD, &Self::CHECK_STAT, &Self::DELTA_BASE_CACHE_LIMIT, &Self::DISAMBIGUATE, diff --git a/vendor/gix/src/config/tree/sections/diff.rs b/vendor/gix/src/config/tree/sections/diff.rs index 7c467b8f5..0ebc13711 100644 --- a/vendor/gix/src/config/tree/sections/diff.rs +++ b/vendor/gix/src/config/tree/sections/diff.rs @@ -1,6 +1,6 @@ use crate::{ config, - config::tree::{keys, Diff, Key, Section}, + config::tree::{keys, Diff, Key, Section, SubSectionRequirement}, }; impl Diff { @@ -17,6 +17,24 @@ impl Diff { ); /// The `diff.renames` key. pub const RENAMES: Renames = Renames::new_renames("renames", &config::Tree::DIFF); + + /// The `diff.<driver>.command` key. + pub const DRIVER_COMMAND: keys::String = keys::String::new_string("command", &config::Tree::DIFF) + .with_subsection_requirement(Some(SubSectionRequirement::Parameter("driver"))); + /// The `diff.<driver>.textconv` key. + pub const DRIVER_TEXTCONV: keys::String = keys::String::new_string("textconv", &config::Tree::DIFF) + .with_subsection_requirement(Some(SubSectionRequirement::Parameter("driver"))); + /// The `diff.<driver>.algorithm` key. + pub const DRIVER_ALGORITHM: Algorithm = + Algorithm::new_with_validate("algorithm", &config::Tree::DIFF, validate::Algorithm) + .with_subsection_requirement(Some(SubSectionRequirement::Parameter("driver"))); + /// The `diff.<driver>.binary` key. + pub const DRIVER_BINARY: Binary = Binary::new_with_validate("binary", &config::Tree::DIFF, validate::Binary) + .with_subsection_requirement(Some(SubSectionRequirement::Parameter("driver"))); + + /// The `diff.external` key. + pub const EXTERNAL: keys::Program = + keys::Program::new_program("external", &config::Tree::DIFF).with_environment_override("GIT_EXTERNAL_DIFF"); } impl Section for Diff { @@ -25,7 +43,16 @@ impl Section for Diff { } fn keys(&self) -> &[&dyn Key] { - &[&Self::ALGORITHM, &Self::RENAME_LIMIT, &Self::RENAMES] + &[ + &Self::ALGORITHM, + &Self::RENAME_LIMIT, + &Self::RENAMES, + &Self::DRIVER_COMMAND, + &Self::DRIVER_TEXTCONV, + &Self::DRIVER_ALGORITHM, + &Self::DRIVER_BINARY, + &Self::EXTERNAL, + ] } } @@ -35,6 +62,9 @@ pub type Algorithm = keys::Any<validate::Algorithm>; /// The `diff.renames` key. pub type Renames = keys::Any<validate::Renames>; +/// The `diff.<driver>.binary` key. +pub type Binary = keys::Any<validate::Binary>; + mod algorithm { use std::borrow::Cow; @@ -67,6 +97,38 @@ mod algorithm { } } +mod binary { + use crate::config::tree::diff::Binary; + + impl Binary { + /// Convert `value` into a tri-state boolean that can take the special value `auto`, resulting in `None`, or is a boolean. + /// If `None` is given, it's treated as implicit boolean `true`, as this method is made to be used + /// with [`gix_config::file::section::Body::value_implicit()`]. + pub fn try_into_binary( + &'static self, + value: Option<std::borrow::Cow<'_, crate::bstr::BStr>>, + ) -> Result<Option<bool>, crate::config::key::GenericErrorWithValue> { + Ok(match value { + None => Some(true), + Some(value) => { + if value.as_ref() == "auto" { + None + } else { + Some( + gix_config::Boolean::try_from(value.as_ref()) + .map(|b| b.0) + .map_err(|err| { + crate::config::key::GenericErrorWithValue::from_value(self, value.into_owned()) + .with_source(err) + })?, + ) + } + } + }) + } + } +} + mod renames { use crate::{ bstr::ByteSlice, @@ -125,4 +187,12 @@ mod validate { Ok(()) } } + + pub struct Binary; + impl keys::Validate for Binary { + fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { + Diff::DRIVER_BINARY.try_into_binary(Some(value.into()))?; + Ok(()) + } + } } diff --git a/vendor/gix/src/config/tree/sections/fetch.rs b/vendor/gix/src/config/tree/sections/fetch.rs index 32db7be5f..9b618913d 100644 --- a/vendor/gix/src/config/tree/sections/fetch.rs +++ b/vendor/gix/src/config/tree/sections/fetch.rs @@ -45,8 +45,7 @@ mod algorithm { &'static self, name: std::borrow::Cow<'_, crate::bstr::BStr>, ) -> Result<crate::remote::fetch::negotiate::Algorithm, crate::config::key::GenericErrorWithValue> { - use crate::bstr::ByteSlice; - use crate::remote::fetch::negotiate::Algorithm; + use crate::{bstr::ByteSlice, remote::fetch::negotiate::Algorithm}; Ok(match name.as_ref().as_bytes() { b"noop" => Algorithm::Noop, diff --git a/vendor/gix/src/config/tree/sections/gitoxide.rs b/vendor/gix/src/config/tree/sections/gitoxide.rs index 966d5af7c..a3b054412 100644 --- a/vendor/gix/src/config/tree/sections/gitoxide.rs +++ b/vendor/gix/src/config/tree/sections/gitoxide.rs @@ -14,6 +14,8 @@ impl Gitoxide { pub const COMMIT: Commit = Commit; /// The `gitoxide.committer` section. pub const COMMITTER: Committer = Committer; + /// The `gitoxide.credentials` section. + pub const CREDENTIALS: Credentials = Credentials; /// The `gitoxide.http` section. pub const HTTP: Http = Http; /// The `gitoxide.https` section. @@ -31,6 +33,9 @@ impl Gitoxide { pub const USER_AGENT: keys::Any = keys::Any::new("userAgent", &config::Tree::GITOXIDE).with_note( "The user agent presented on the git protocol layer, serving as fallback for when no `http.userAgent` is set", ); + /// The `gitoxide.tracePacket` Key. + pub const TRACE_PACKET: keys::Boolean = keys::Boolean::new_boolean("tracePacket", &config::Tree::GITOXIDE) + .with_environment_override("GIT_TRACE_PACKET"); } impl Section for Gitoxide { @@ -39,7 +44,7 @@ impl Section for Gitoxide { } fn keys(&self) -> &[&dyn Key] { - &[&Self::USER_AGENT] + &[&Self::USER_AGENT, &Self::TRACE_PACKET] } fn sub_sections(&self) -> &[&dyn Section] { @@ -49,6 +54,7 @@ impl Section for Gitoxide { &Self::CORE, &Self::COMMIT, &Self::COMMITTER, + &Self::CREDENTIALS, &Self::HTTP, &Self::HTTPS, &Self::OBJECTS, @@ -69,6 +75,20 @@ mod subsections { #[derive(Copy, Clone, Default)] pub struct Core; + /// The `gitoxide.allow.protocolFromUser` key. + pub type RefsNamespace = keys::Any<super::validate::RefsNamespace>; + + impl RefsNamespace { + /// Derive the negotiation algorithm identified by `name`, case-sensitively. + pub fn try_into_refs_namespace( + &'static self, + name: std::borrow::Cow<'_, crate::bstr::BStr>, + ) -> Result<gix_ref::Namespace, crate::config::refs_namespace::Error> { + gix_ref::namespace::expand(name.as_ref()) + .map_err(|err| crate::config::key::Error::from_value(self, name.into_owned()).with_source(err)) + } + } + impl Core { /// The `gitoxide.core.defaultPackCacheMemoryLimit` key. pub const DEFAULT_PACK_CACHE_MEMORY_LIMIT: keys::UnsignedInteger = @@ -95,6 +115,20 @@ mod subsections { /// It controls whether or not long running filter driver processes can use the 'delay' capability. pub const FILTER_PROCESS_DELAY: keys::Boolean = keys::Boolean::new_boolean("filterProcessDelay", &Gitoxide::CORE); + + /// The `gitoxide.core.externalCommandStderr` key (default `true`). + /// + /// If `true`, the default, `stderr` of worktree filter programs, or any other git-context bearing command + /// invoked will be inherited. + /// If `false`, it will be suppressed completely. + pub const EXTERNAL_COMMAND_STDERR: keys::Boolean = + keys::Boolean::new_boolean("externalCommandStderr", &Gitoxide::CORE) + .with_environment_override("GIX_EXTERNAL_COMMAND_STDERR"); + + /// The `gitoxide.core.refsNamespace` key. + pub const REFS_NAMESPACE: RefsNamespace = + keys::Any::new_with_validate("refsNamespace", &Gitoxide::CORE, super::validate::RefsNamespace) + .with_environment_override("GIT_NAMESPACE"); } impl Section for Core { @@ -109,6 +143,8 @@ mod subsections { &Self::USE_STDEV, &Self::SHALLOW_FILE, &Self::FILTER_PROCESS_DELAY, + &Self::EXTERNAL_COMMAND_STDERR, + &Self::REFS_NAMESPACE, ] } @@ -154,6 +190,14 @@ mod subsections { http::SslVersion::new_ssl_version("sslVersionMax", &Gitoxide::HTTP).with_note( "entirely new to set the upper bound for the allowed ssl version range. Overwrites the max bound of `http.sslVersion` if set. Min and Max must be set to become effective.", ); + /// The `gitoxide.http.sslNoVerify` key. + /// + /// If set, disable SSL verification. Using this is discouraged as it can lead to + /// various security risks. An example where this may be needed is when an internal + /// git server uses a self-signed certificate and the user accepts the associated security risks. + pub const SSL_NO_VERIFY: keys::Boolean = keys::Boolean::new_boolean("sslNoVerify", &Gitoxide::HTTP) + .with_environment_override("GIT_SSL_NO_VERIFY") + .with_note("used to disable SSL verification. When this is enabled it takes priority over http.sslVerify"); /// The `gitoxide.http.proxyAuthMethod` key. pub const PROXY_AUTH_METHOD: http::ProxyAuthMethod = http::ProxyAuthMethod::new_proxy_auth_method("proxyAuthMethod", &Gitoxide::HTTP) @@ -174,6 +218,7 @@ mod subsections { &Self::CONNECT_TIMEOUT, &Self::SSL_VERSION_MIN, &Self::SSL_VERSION_MAX, + &Self::SSL_NO_VERIFY, &Self::PROXY_AUTH_METHOD, ] } @@ -375,7 +420,7 @@ mod subsections { pub const CACHE_LIMIT: keys::UnsignedInteger = keys::UnsignedInteger::new_unsigned_integer("cacheLimit", &Gitoxide::OBJECTS) .with_note("If unset or 0, there is no object cache") - .with_environment_override("GITOXIDE_OBJECT_CACHE_MEMORY"); + .with_environment_override("GIX_OBJECT_CACHE_MEMORY"); /// The `gitoxide.objects.noReplace` key. pub const NO_REPLACE: keys::Boolean = keys::Boolean::new_boolean("noReplace", &Gitoxide::OBJECTS); /// The `gitoxide.objects.replaceRefBase` key. @@ -424,6 +469,37 @@ mod subsections { } } + /// The `credentials` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Credentials; + impl Credentials { + /// The `gitoxide.credentials.terminalPrompt` key. + pub const TERMINAL_PROMPT: keys::Boolean = keys::Boolean::new_boolean("terminalPrompt", &Gitoxide::CREDENTIALS) + .with_note("This is a custom addition to provide an alternative to the respective environment variable.") + .with_environment_override("GIT_TERMINAL_PROMPT"); + + /// The `gitoxide.credentials.helperStderr` key to control what happens with the credential helpers `stderr`. + /// + /// If `true`, the default, `stderr` of credential helper programs will be inherited, just like with `git`. + /// If `false`, will be suppressed completely. + pub const HELPER_STDERR: keys::Boolean = keys::Boolean::new_boolean("helperStderr", &Gitoxide::CREDENTIALS) + .with_environment_override("GIX_CREDENTIALS_HELPER_STDERR"); + } + + impl Section for Credentials { + fn name(&self) -> &str { + "credentials" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::TERMINAL_PROMPT, &Self::HELPER_STDERR] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + /// The `commit` sub-section. #[derive(Copy, Clone, Default)] pub struct Commit; @@ -451,7 +527,7 @@ mod subsections { } } } -pub use subsections::{Allow, Author, Commit, Committer, Core, Http, Https, Objects, Pathspec, Ssh, User}; +pub use subsections::{Allow, Author, Commit, Committer, Core, Credentials, Http, Https, Objects, Pathspec, Ssh, User}; pub mod validate { use std::error::Error; @@ -467,4 +543,12 @@ pub mod validate { Ok(()) } } + + pub struct RefsNamespace; + impl Validate for RefsNamespace { + fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { + super::Core::REFS_NAMESPACE.try_into_refs_namespace(value.into())?; + Ok(()) + } + } } diff --git a/vendor/gix/src/config/tree/sections/http.rs b/vendor/gix/src/config/tree/sections/http.rs index f45c37076..4fc733564 100644 --- a/vendor/gix/src/config/tree/sections/http.rs +++ b/vendor/gix/src/config/tree/sections/http.rs @@ -10,6 +10,9 @@ impl Http { .with_deviation( "accepts the new 'default' value which means to use the curl default just like the empty string does", ); + /// The `http.sslVerify` key. + pub const SSL_VERIFY: keys::Boolean = keys::Boolean::new_boolean("sslVerify", &config::Tree::HTTP) + .with_note("also see the `gitoxide.http.sslNoVerify` key"); /// The `http.proxy` key. pub const PROXY: keys::String = keys::String::new_string("proxy", &config::Tree::HTTP).with_deviation("fails on strings with illformed UTF-8"); @@ -58,6 +61,7 @@ impl Section for Http { fn keys(&self) -> &[&dyn Key] { &[ &Self::SSL_VERSION, + &Self::SSL_VERIFY, &Self::PROXY, &Self::PROXY_AUTH_METHOD, &Self::VERSION, diff --git a/vendor/gix/src/create.rs b/vendor/gix/src/create.rs index 1ef63b9aa..00971ec05 100644 --- a/vendor/gix/src/create.rs +++ b/vendor/gix/src/create.rs @@ -230,7 +230,7 @@ pub fn into( Ok(gix_discover::repository::Path::from_dot_git_dir( dot_git, if bare { - gix_discover::repository::Kind::Bare + gix_discover::repository::Kind::PossiblyBare } else { gix_discover::repository::Kind::WorkTree { linked_git_dir: None } }, diff --git a/vendor/gix/src/diff.rs b/vendor/gix/src/diff.rs index b10819293..af3c98704 100644 --- a/vendor/gix/src/diff.rs +++ b/vendor/gix/src/diff.rs @@ -15,3 +15,130 @@ pub mod rename { RenamesAndCopies, } } + +/// +#[cfg(feature = "blob-diff")] +mod utils { + use gix_diff::{rewrites::Copies, Rewrites}; + + use crate::{ + config::{cache::util::ApplyLeniency, tree::Diff}, + diff::rename::Tracking, + Repository, + }; + + /// + pub mod new_rewrites { + /// The error returned by [`new_rewrites()`](super::new_rewrites()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + ConfigDiffRenames(#[from] crate::config::key::GenericError), + #[error(transparent)] + ConfigDiffRenameLimit(#[from] crate::config::unsigned_integer::Error), + } + } + + /// + pub mod resource_cache { + /// The error returned by [`resource_cache()`](super::resource_cache()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + DiffAlgorithm(#[from] crate::config::diff::algorithm::Error), + #[error(transparent)] + WorktreeFilterOptions(#[from] crate::filter::pipeline::options::Error), + #[error(transparent)] + DiffDrivers(#[from] crate::config::diff::drivers::Error), + #[error(transparent)] + DiffPipelineOptions(#[from] crate::config::diff::pipeline_options::Error), + #[error(transparent)] + CommandContext(#[from] crate::config::command_context::Error), + #[error(transparent)] + AttributeStack(#[from] crate::config::attribute_stack::Error), + } + } + + /// Create an instance by reading all relevant information from the `config`uration, while being `lenient` or not. + /// Returns `Ok(None)` if nothing is configured. + /// + /// Note that missing values will be defaulted similar to what git does. + #[allow(clippy::result_large_err)] + pub fn new_rewrites( + config: &gix_config::File<'static>, + lenient: bool, + ) -> Result<Option<Rewrites>, new_rewrites::Error> { + let key = "diff.renames"; + let copies = match config + .boolean_by_key(key) + .map(|value| Diff::RENAMES.try_into_renames(value)) + .transpose() + .with_leniency(lenient)? + { + Some(renames) => match renames { + Tracking::Disabled => return Ok(None), + Tracking::Renames => None, + Tracking::RenamesAndCopies => Some(Copies::default()), + }, + None => return Ok(None), + }; + + let default = Rewrites::default(); + Ok(Rewrites { + copies, + limit: config + .integer_by_key("diff.renameLimit") + .map(|value| Diff::RENAME_LIMIT.try_into_usize(value)) + .transpose() + .with_leniency(lenient)? + .unwrap_or(default.limit), + ..default + } + .into()) + } + + /// Return a low-level utility to efficiently prepare a the blob-level diff operation between two resources, + /// and cache these diffable versions so that matrix-like MxN diffs are efficient. + /// + /// `repo` is used to obtain the needed configuration values, and `index` is used to potentially read `.gitattributes` + /// files from which may affect the diff operation. + /// `mode` determines how the diffable files will look like, and also how fast, in average, these conversions are. + /// `attribute_source` controls where `.gitattributes` will be read from, and it's typically adjusted based on the + /// `roots` - if there are no worktree roots, `.gitattributes` are also not usually read from worktrees. + /// `roots` provide information about where to get diffable data from, so source and destination can either be sourced from + /// a worktree, or from the object database, or both. + pub fn resource_cache( + repo: &Repository, + index: &gix_index::State, + mode: gix_diff::blob::pipeline::Mode, + attribute_source: gix_worktree::stack::state::attributes::Source, + roots: gix_diff::blob::pipeline::WorktreeRoots, + ) -> Result<gix_diff::blob::Platform, resource_cache::Error> { + let diff_algo = repo.config.diff_algorithm()?; + let diff_cache = gix_diff::blob::Platform::new( + gix_diff::blob::platform::Options { + algorithm: Some(diff_algo), + skip_internal_diff_if_external_is_configured: false, + }, + gix_diff::blob::Pipeline::new( + roots, + gix_filter::Pipeline::new(repo.command_context()?, crate::filter::Pipeline::options(repo)?), + repo.config.diff_drivers()?, + repo.config.diff_pipeline_options()?, + ), + mode, + repo.attributes_only( + // TODO(perf): this could benefit from not having to build an intermediate index, + // and traverse the a tree directly. + index, + attribute_source, + )? + .inner, + ); + Ok(diff_cache) + } +} +#[cfg(feature = "blob-diff")] +pub use utils::{new_rewrites, resource_cache}; diff --git a/vendor/gix/src/ext/object_id.rs b/vendor/gix/src/ext/object_id.rs index a4515022b..d4d946766 100644 --- a/vendor/gix/src/ext/object_id.rs +++ b/vendor/gix/src/ext/object_id.rs @@ -8,10 +8,9 @@ pub type AncestorsIter<Find> = Ancestors<Find, fn(&gix_hash::oid) -> bool, ances /// An extension trait to add functionality to [`ObjectId`]s. pub trait ObjectIdExt: Sealed { /// Create an iterator over the ancestry of the commits reachable from this id, which must be a commit. - fn ancestors<Find, E>(self, find: Find) -> AncestorsIter<Find> + fn ancestors<Find>(self, find: Find) -> AncestorsIter<Find> where - Find: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Result<gix_object::CommitRefIter<'a>, E>, - E: std::error::Error + Send + Sync + 'static; + Find: gix_object::Find; /// Infuse this object id `repo` access. fn attach(self, repo: &crate::Repository) -> crate::Id<'_>; @@ -20,10 +19,9 @@ pub trait ObjectIdExt: Sealed { impl Sealed for ObjectId {} impl ObjectIdExt for ObjectId { - fn ancestors<Find, E>(self, find: Find) -> AncestorsIter<Find> + fn ancestors<Find>(self, find: Find) -> AncestorsIter<Find> where - Find: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Result<gix_object::CommitRefIter<'a>, E>, - E: std::error::Error + Send + Sync + 'static, + Find: gix_object::Find, { Ancestors::new(Some(self), ancestors::State::default(), find) } diff --git a/vendor/gix/src/ext/rev_spec.rs b/vendor/gix/src/ext/rev_spec.rs index ed7dc0460..caa58e2c7 100644 --- a/vendor/gix/src/ext/rev_spec.rs +++ b/vendor/gix/src/ext/rev_spec.rs @@ -12,6 +12,7 @@ impl RevSpecExt for gix_revision::Spec { fn attach(self, repo: &crate::Repository) -> crate::revision::Spec<'_> { crate::revision::Spec { inner: self, + path: None, first_ref: None, second_ref: None, repo, diff --git a/vendor/gix/src/ext/tree.rs b/vendor/gix/src/ext/tree.rs index 56b832b84..9aacc9d58 100644 --- a/vendor/gix/src/ext/tree.rs +++ b/vendor/gix/src/ext/tree.rs @@ -1,6 +1,5 @@ use std::borrow::BorrowMut; -use gix_hash::oid; use gix_object::TreeRefIter; use gix_traverse::tree::breadthfirst; @@ -16,11 +15,11 @@ pub trait TreeIterExt: Sealed { fn traverse<StateMut, Find, V>( &self, state: StateMut, - find: Find, + objects: Find, delegate: &mut V, ) -> Result<(), breadthfirst::Error> where - Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>, + Find: gix_object::Find, StateMut: BorrowMut<breadthfirst::State>, V: gix_traverse::tree::Visit; } @@ -31,15 +30,15 @@ impl<'d> TreeIterExt for TreeRefIter<'d> { fn traverse<StateMut, Find, V>( &self, state: StateMut, - find: Find, + objects: Find, delegate: &mut V, ) -> Result<(), breadthfirst::Error> where - Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>, + Find: gix_object::Find, StateMut: BorrowMut<breadthfirst::State>, V: gix_traverse::tree::Visit, { - breadthfirst(self.clone(), state, find, delegate) + breadthfirst(*self, state, objects, delegate) } } diff --git a/vendor/gix/src/filter.rs b/vendor/gix/src/filter.rs index b106840a7..f05e332a2 100644 --- a/vendor/gix/src/filter.rs +++ b/vendor/gix/src/filter.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; pub use gix_filter as plumbing; -use gix_odb::{Find, FindExt}; +use gix_object::Find; use crate::{ bstr::BStr, @@ -32,6 +32,8 @@ pub mod pipeline { name: BString, source: gix_config::value::Error, }, + #[error(transparent)] + CommandContext(#[from] config::command_context::Error), } } @@ -111,7 +113,7 @@ impl<'repo> Pipeline<'repo> { /// Create a new instance by extracting all necessary information and configuration from a `repo` along with `cache` for accessing /// attributes. The `index` is used for some filters which may access it under very specific circumstances. pub fn new(repo: &'repo Repository, cache: gix_worktree::Stack) -> Result<Self, pipeline::options::Error> { - let pipeline = gix_filter::Pipeline::new(cache.attributes_collection(), Self::options(repo)?); + let pipeline = gix_filter::Pipeline::new(repo.command_context()?, Self::options(repo)?); Ok(Pipeline { inner: pipeline, cache, @@ -141,16 +143,14 @@ impl<'repo> Pipeline<'repo> { where R: std::io::Read, { - let entry = self - .cache - .at_path(rela_path, Some(false), |id, buf| self.repo.objects.find_blob(id, buf))?; + let entry = self.cache.at_path(rela_path, Some(false), &self.repo.objects)?; Ok(self.inner.convert_to_git( src, rela_path, &mut |_, attrs| { entry.matching_attributes(attrs); }, - &mut |buf| -> Result<_, gix_odb::find::Error> { + &mut |buf| -> Result<_, gix_object::find::Error> { let entry = match index.entry_by_path(gix_path::into_bstr(rela_path).as_ref()) { None => return Ok(None), Some(entry) => entry, @@ -175,9 +175,7 @@ impl<'repo> Pipeline<'repo> { can_delay: gix_filter::driver::apply::Delay, ) -> Result<gix_filter::pipeline::convert::ToWorktreeOutcome<'input, '_>, pipeline::convert_to_worktree::Error> { - let entry = self - .cache - .at_entry(rela_path, Some(false), |id, buf| self.repo.objects.find_blob(id, buf))?; + let entry = self.cache.at_entry(rela_path, Some(false), &self.repo.objects)?; Ok(self.inner.convert_to_worktree( src, rela_path, diff --git a/vendor/gix/src/head/peel.rs b/vendor/gix/src/head/peel.rs index 88e23636f..4ee116ed0 100644 --- a/vendor/gix/src/head/peel.rs +++ b/vendor/gix/src/head/peel.rs @@ -1,12 +1,14 @@ use crate::{ ext::{ObjectIdExt, ReferenceExt}, + head::Kind, Head, }; 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::try_peel_to_id_in_place()) + /// and [`Head::into_fully_peeled_id()`](super::Head::try_into_peeled_id()). #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -19,13 +21,11 @@ mod error { pub use error::Error; -use crate::head::Kind; - /// -pub mod to_commit { +pub mod into_id { 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::into_peeled_id()`](super::Head::into_peeled_id()). #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -38,86 +38,122 @@ pub mod to_commit { } } +/// +pub mod to_commit { + use crate::object; + + /// 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 { + #[error(transparent)] + PeelToObject(#[from] super::to_object::Error), + #[error(transparent)] + ObjectKind(#[from] object::try_into::Error), + } +} + +/// +pub mod to_object { + /// The error returned by [`Head::peel_to_object_in_place()`](super::Head::peel_to_object_in_place()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Peel(#[from] super::Error), + #[error("Branch '{name}' does not have any commits")] + Unborn { name: gix_ref::FullName }, + } +} + impl<'repo> Head<'repo> { - // TODO: tests - /// Peel this instance to make obtaining its final target id possible, while returning an error on unborn heads. - pub fn peeled(mut self) -> Result<Self, Error> { - self.peel_to_id_in_place().transpose()?; - Ok(self) + /// Peel this instance and consume it to make obtaining its final target id possible, while returning an error on unborn heads. + /// + /// The final target is obtained by following symbolic references and peeling tags to their final destination, which + /// typically is a commit, but can be any object. + pub fn into_peeled_id(mut self) -> Result<crate::Id<'repo>, into_id::Error> { + self.try_peel_to_id_in_place()?; + self.id().ok_or_else(|| match self.kind { + Kind::Symbolic(gix_ref::Reference { name, .. }) | Kind::Unborn(name) => into_id::Error::Unborn { name }, + Kind::Detached { .. } => unreachable!("id can be returned after peeling"), + }) + } + + /// Peel this instance and consume it to make obtaining its final target object possible, while returning an error on unborn heads. + /// + /// The final target is obtained by following symbolic references and peeling tags to their final destination, which + /// typically is a commit, but can be any object as well. + pub fn into_peeled_object(mut self) -> Result<crate::Object<'repo>, to_object::Error> { + self.peel_to_object_in_place() + } + + /// Consume this instance and transform it into the final object that it points to, or `Ok(None)` if the `HEAD` + /// reference is yet to be born. + /// + /// The final target is obtained by following symbolic references and peeling tags to their final destination, which + /// typically is a commit, but can be any object. + pub fn try_into_peeled_id(mut self) -> Result<Option<crate::Id<'repo>>, Error> { + self.try_peel_to_id_in_place() } - // TODO: tests - // TODO: Fix this! It's not consistently peeling tags. The whole peeling business should be reconsidered to do what people usually - // want which is to peel references, if present, and then peel objects with control over which object type to end at. - // Finding a good interface for that isn't easy as ideally, it's an iterator that shows the intermediate objects so the user - // can select which tag of a chain to choose. /// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no /// more object to follow, and return that object id. /// - /// Returns `None` if the head is unborn. - pub fn peel_to_id_in_place(&mut self) -> Option<Result<crate::Id<'repo>, Error>> { - Some(match &mut self.kind { - Kind::Unborn(_name) => return None, + /// Returns `Ok(None)` if the head is unborn. + /// + /// The final target is obtained by following symbolic references and peeling tags to their final destination, which + /// typically is a commit, but can be any object. + pub fn try_peel_to_id_in_place(&mut self) -> Result<Option<crate::Id<'repo>>, Error> { + Ok(Some(match &mut self.kind { + Kind::Unborn(_name) => return Ok(None), Kind::Detached { peeled: Some(peeled), .. - } => Ok((*peeled).attach(self.repo)), + } => (*peeled).attach(self.repo), Kind::Detached { peeled: None, target } => { - match target - .attach(self.repo) - .object() - .map_err(Into::into) - .and_then(|obj| obj.peel_tags_to_end().map_err(Into::into)) - .map(|peeled| peeled.id) - { - Ok(peeled) => { - self.kind = Kind::Detached { - peeled: Some(peeled), - target: *target, - }; - Ok(peeled.attach(self.repo)) + let id = target.attach(self.repo); + if id.header()?.kind() == gix_object::Kind::Commit { + id + } else { + match id.object()?.peel_tags_to_end() { + Ok(obj) => { + self.kind = Kind::Detached { + peeled: Some(obj.id), + target: *target, + }; + obj.id() + } + Err(err) => return Err(err.into()), } - Err(err) => Err(err), } } Kind::Symbolic(r) => { let mut nr = r.clone().attach(self.repo); - let peeled = nr.peel_to_id_in_place().map_err(Into::into); + let peeled = nr.peel_to_id_in_place(); *r = nr.detach(); - peeled + peeled? } - }) + })) } - // TODO: tests - // TODO: something similar in `crate::Reference` /// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no /// more object to follow, transform the id into a commit if possible and return that. /// /// Returns an error if the head is unborn or if it doesn't point to a commit. - pub fn peel_to_commit_in_place(&mut self) -> Result<crate::Commit<'repo>, to_commit::Error> { - let id = self.peel_to_id_in_place().ok_or_else(|| to_commit::Error::Unborn { - name: self.referent_name().expect("unborn").to_owned(), - })??; + pub fn peel_to_object_in_place(&mut self) -> Result<crate::Object<'repo>, to_object::Error> { + let id = self + .try_peel_to_id_in_place()? + .ok_or_else(|| to_object::Error::Unborn { + name: self.referent_name().expect("unborn").to_owned(), + })?; id.object() - .map_err(|err| to_commit::Error::Peel(Error::FindExistingObject(err))) - .and_then(|object| object.try_into_commit().map_err(Into::into)) + .map_err(|err| to_object::Error::Peel(Error::FindExistingObject(err))) } - /// Consume this instance and transform it into the final object that it points to, or `None` if the `HEAD` - /// reference is yet to be born. - pub fn into_fully_peeled_id(self) -> Option<Result<crate::Id<'repo>, Error>> { - Some(match self.kind { - Kind::Unborn(_name) => return None, - Kind::Detached { - peeled: Some(peeled), .. - } => Ok(peeled.attach(self.repo)), - Kind::Detached { peeled: None, target } => target - .attach(self.repo) - .object() - .map_err(Into::into) - .and_then(|obj| obj.peel_tags_to_end().map_err(Into::into)) - .map(|obj| obj.id.attach(self.repo)), - Kind::Symbolic(r) => r.attach(self.repo).peel_to_id_in_place().map_err(Into::into), - }) + /// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no + /// more object to follow, transform the id into a commit if possible and return that. + /// + /// Returns an error if the head is unborn or if it doesn't point to a commit. + pub fn peel_to_commit_in_place(&mut self) -> Result<crate::Commit<'repo>, to_commit::Error> { + Ok(self.peel_to_object_in_place()?.try_into_commit()?) } } diff --git a/vendor/gix/src/lib.rs b/vendor/gix/src/lib.rs index 8218defc8..d4928b9de 100644 --- a/vendor/gix/src/lib.rs +++ b/vendor/gix/src/lib.rs @@ -82,10 +82,10 @@ //! //! ### Feature Flags #![cfg_attr( - feature = "document-features", - cfg_attr(doc, doc = ::document_features::document_features!()) + all(doc, feature = "document-features"), + doc = ::document_features::document_features!() )] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))] #![deny(missing_docs, rust_2018_idioms, unsafe_code)] #![allow(clippy::result_large_err)] diff --git a/vendor/gix/src/object/blob.rs b/vendor/gix/src/object/blob.rs index ef19c302a..59629727f 100644 --- a/vendor/gix/src/object/blob.rs +++ b/vendor/gix/src/object/blob.rs @@ -5,61 +5,132 @@ use crate::{Blob, ObjectDetached}; pub mod diff { use std::ops::Range; - use crate::{bstr::ByteSlice, object::blob::diff::line::Change}; + use gix_diff::blob::{platform::prepare_diff::Operation, ResourceKind}; + + use crate::{ + bstr::ByteSlice, + object::{blob::diff::lines::Change, tree::diff::change::Event}, + }; /// A platform to keep temporary information to perform line diffs on modified blobs. /// - pub struct Platform<'old, 'new> { - /// The previous version of the blob. - pub old: crate::Object<'old>, - /// The new version of the blob. - pub new: crate::Object<'new>, - /// The algorithm to use when calling [imara_diff::diff()][gix_diff::blob::diff()]. - /// This value is determined by the `diff.algorithm` configuration. - pub algo: gix_diff::blob::Algorithm, + pub struct Platform<'a> { + /// The cache holding diffable data related to our blobs. + pub resource_cache: &'a mut gix_diff::blob::Platform, } /// pub mod init { - /// The error returned by [`Platform::from_ids()`][super::Platform::from_ids()]. - #[derive(Debug, thiserror::Error)] - #[allow(missing_docs)] - pub enum Error { - #[error("Could not find the previous blob or the new blob to diff against")] - FindExisting(#[from] crate::object::find::existing::Error), - #[error("Could not obtain diff algorithm from configuration")] - DiffAlgorithm(#[from] crate::config::diff::algorithm::Error), - } + /// The error returned by [`Platform::from_tree_change()`][super::Platform::from_tree_change()]. + pub type Error = gix_diff::blob::platform::set_resource::Error; } - impl<'old, 'new> Platform<'old, 'new> { - /// Produce a platform for performing various diffs after obtaining the object data of `previous_id` and `new_id`. - /// - /// Note that these objects are treated as raw data and are assumed to be blobs. - pub fn from_ids( - previous_id: &crate::Id<'old>, - new_id: &crate::Id<'new>, - ) -> Result<Platform<'old, 'new>, init::Error> { - match previous_id - .object() - .and_then(|old| new_id.object().map(|new| (old, new))) - { - Ok((old, new)) => { - let algo = match new_id.repo.config.diff_algorithm() { - Ok(algo) => algo, - Err(err) => return Err(err.into()), - }; - Ok(Platform { old, new, algo }) + impl<'a> Platform<'a> { + /// Produce a platform for performing various diffs after obtaining the data from a single `tree_change`. + pub fn from_tree_change( + tree_change: &crate::object::tree::diff::Change<'_, '_, '_>, + resource_cache: &'a mut gix_diff::blob::Platform, + ) -> Result<Platform<'a>, init::Error> { + match tree_change.event { + Event::Addition { entry_mode, id } => { + resource_cache.set_resource( + id.repo.object_hash().null(), + entry_mode.kind(), + tree_change.location, + ResourceKind::OldOrSource, + &id.repo.objects, + )?; + resource_cache.set_resource( + id.inner, + entry_mode.kind(), + tree_change.location, + ResourceKind::NewOrDestination, + &id.repo.objects, + )?; + } + Event::Deletion { entry_mode, id } => { + resource_cache.set_resource( + id.inner, + entry_mode.kind(), + tree_change.location, + ResourceKind::OldOrSource, + &id.repo.objects, + )?; + resource_cache.set_resource( + id.repo.object_hash().null(), + entry_mode.kind(), + tree_change.location, + ResourceKind::NewOrDestination, + &id.repo.objects, + )?; + } + Event::Modification { + previous_entry_mode, + previous_id, + entry_mode, + id, + } => { + resource_cache.set_resource( + previous_id.inner, + previous_entry_mode.kind(), + tree_change.location, + ResourceKind::OldOrSource, + &previous_id.repo.objects, + )?; + resource_cache.set_resource( + id.inner, + entry_mode.kind(), + tree_change.location, + ResourceKind::NewOrDestination, + &id.repo.objects, + )?; + } + Event::Rewrite { + source_location, + source_entry_mode, + source_id, + entry_mode, + id, + diff: _, + copy: _, + } => { + resource_cache.set_resource( + source_id.inner, + source_entry_mode.kind(), + source_location, + ResourceKind::OldOrSource, + &source_id.repo.objects, + )?; + resource_cache.set_resource( + id.inner, + entry_mode.kind(), + tree_change.location, + ResourceKind::NewOrDestination, + &id.repo.objects, + )?; } - Err(err) => Err(err.into()), } + Ok(Self { resource_cache }) } } /// - pub mod line { + pub mod lines { use crate::bstr::BStr; + /// The error returned by [Platform::lines()](super::Platform::lines()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error<E> + where + E: std::error::Error + Send + Sync + 'static, + { + #[error(transparent)] + ProcessHunk(E), + #[error(transparent)] + PrepareDiff(#[from] gix_diff::blob::platform::prepare_diff::Error), + } + /// A change to a hunk of lines. pub enum Change<'a, 'data> { /// Lines were added. @@ -82,70 +153,91 @@ pub mod diff { } } - impl<'old, 'new> Platform<'old, 'new> { + impl<'a> Platform<'a> { /// Perform a diff on lines between the old and the new version of a blob, passing each hunk of lines to `process_hunk`. - /// The diffing algorithm is determined by the `diff.algorithm` configuration. - /// - /// Note that you can invoke the diff more flexibly as well. + /// The diffing algorithm is determined by the `diff.algorithm` configuration, or individual diff drivers. + /// Note that `process_hunk` is not called if one of the involved resources are binary, but that can be determined + /// by introspecting the outcome. // TODO: more tests (only tested insertion right now) - pub fn lines<FnH, E>(&self, mut process_hunk: FnH) -> Result<(), E> + pub fn lines<FnH, E>( + &mut self, + mut process_hunk: FnH, + ) -> Result<gix_diff::blob::platform::prepare_diff::Outcome<'_>, lines::Error<E>> where - FnH: FnMut(line::Change<'_, '_>) -> Result<(), E>, - E: std::error::Error, + FnH: FnMut(lines::Change<'_, '_>) -> Result<(), E>, + E: std::error::Error + Send + Sync + 'static, { - let input = self.line_tokens(); - let mut err = None; - let mut lines = Vec::new(); - gix_diff::blob::diff(self.algo, &input, |before: Range<u32>, after: Range<u32>| { - if err.is_some() { - return; + self.resource_cache.options.skip_internal_diff_if_external_is_configured = false; + + let prep = self.resource_cache.prepare_diff()?; + match prep.operation { + Operation::InternalDiff { algorithm } => { + let input = prep.interned_input(); + let mut err = None; + let mut lines = Vec::new(); + + gix_diff::blob::diff(algorithm, &input, |before: Range<u32>, after: Range<u32>| { + if err.is_some() { + return; + } + lines.clear(); + lines.extend( + input.before[before.start as usize..before.end as usize] + .iter() + .map(|&line| input.interner[line].as_bstr()), + ); + let end_of_before = lines.len(); + lines.extend( + input.after[after.start as usize..after.end as usize] + .iter() + .map(|&line| input.interner[line].as_bstr()), + ); + let hunk_before = &lines[..end_of_before]; + let hunk_after = &lines[end_of_before..]; + if hunk_after.is_empty() { + err = process_hunk(Change::Deletion { lines: hunk_before }).err(); + } else if hunk_before.is_empty() { + err = process_hunk(Change::Addition { lines: hunk_after }).err(); + } else { + err = process_hunk(Change::Modification { + lines_before: hunk_before, + lines_after: hunk_after, + }) + .err(); + } + }); + + if let Some(err) = err { + return Err(lines::Error::ProcessHunk(err)); + } } - lines.clear(); - lines.extend( - input.before[before.start as usize..before.end as usize] - .iter() - .map(|&line| input.interner[line].as_bstr()), - ); - let end_of_before = lines.len(); - lines.extend( - input.after[after.start as usize..after.end as usize] - .iter() - .map(|&line| input.interner[line].as_bstr()), - ); - let hunk_before = &lines[..end_of_before]; - let hunk_after = &lines[end_of_before..]; - if hunk_after.is_empty() { - err = process_hunk(Change::Deletion { lines: hunk_before }).err(); - } else if hunk_before.is_empty() { - err = process_hunk(Change::Addition { lines: hunk_after }).err(); - } else { - err = process_hunk(Change::Modification { - lines_before: hunk_before, - lines_after: hunk_after, - }) - .err(); + Operation::ExternalCommand { .. } => { + unreachable!("we disabled that") } - }); - - match err { - Some(err) => Err(err), - None => Ok(()), - } + Operation::SourceOrDestinationIsBinary => {} + }; + Ok(prep) } /// Count the amount of removed and inserted lines efficiently. - pub fn line_counts(&self) -> gix_diff::blob::sink::Counter<()> { - let tokens = self.line_tokens(); - gix_diff::blob::diff(self.algo, &tokens, gix_diff::blob::sink::Counter::default()) - } + /// Note that nothing will happen if one of the inputs is binary, and `None` will be returned. + pub fn line_counts( + &mut self, + ) -> Result<Option<gix_diff::blob::sink::Counter<()>>, gix_diff::blob::platform::prepare_diff::Error> { + self.resource_cache.options.skip_internal_diff_if_external_is_configured = false; - /// Return a tokenizer which treats lines as smallest unit for use in a [diff operation][gix_diff::blob::diff()]. - /// - /// The line separator is determined according to normal git rules and filters. - pub fn line_tokens(&self) -> gix_diff::blob::intern::InternedInput<&[u8]> { - // TODO: make use of `core.eol` and/or filters to do line-counting correctly. It's probably - // OK to just know how these objects are saved to know what constitutes a line. - gix_diff::blob::intern::InternedInput::new(self.old.data.as_bytes(), self.new.data.as_bytes()) + let prep = self.resource_cache.prepare_diff()?; + match prep.operation { + Operation::InternalDiff { algorithm } => { + let tokens = prep.interned_input(); + let counter = gix_diff::blob::diff(algorithm, &tokens, gix_diff::blob::sink::Counter::default()); + Ok(Some(counter)) + } + Operation::ExternalCommand { .. } => { + unreachable!("we disabled that") + } + Operation::SourceOrDestinationIsBinary => Ok(None), + } } } } diff --git a/vendor/gix/src/object/errors.rs b/vendor/gix/src/object/errors.rs index 92789b6cb..db81daacb 100644 --- a/vendor/gix/src/object/errors.rs +++ b/vendor/gix/src/object/errors.rs @@ -20,12 +20,12 @@ pub mod find { /// Indicate that an error occurred when trying to find an object. #[derive(Debug, thiserror::Error)] #[error(transparent)] - pub struct Error(#[from] pub gix_odb::find::Error); + pub struct Error(#[from] pub gix_object::find::Error); /// pub mod existing { /// An object could not be found in the database, or an error occurred when trying to obtain it. - pub type Error = gix_odb::find::existing::Error; + pub type Error = gix_object::find::existing::Error; } } @@ -34,5 +34,5 @@ pub mod write { /// An error to indicate writing to the loose object store failed. #[derive(Debug, thiserror::Error)] #[error(transparent)] - pub struct Error(#[from] pub gix_odb::find::Error); + pub struct Error(#[from] pub gix_object::find::Error); } diff --git a/vendor/gix/src/object/tree/diff/change.rs b/vendor/gix/src/object/tree/diff/change.rs index e6826d6ed..e4eb5f3df 100644 --- a/vendor/gix/src/object/tree/diff/change.rs +++ b/vendor/gix/src/object/tree/diff/change.rs @@ -1,17 +1,4 @@ -use crate::{bstr::BStr, Id}; - -/// Information about the diff performed to detect similarity of a [Rewrite][Event::Rewrite]. -#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] -pub struct DiffLineStats { - /// The amount of lines to remove from the source to get to the destination. - pub removals: u32, - /// The amount of lines to add to the source to get to the destination. - pub insertions: u32, - /// The amount of lines of the previous state, in the source. - pub before: u32, - /// The amount of lines of the new state, in the destination. - pub after: u32, -} +use crate::{bstr::BStr, diff::blob::DiffLineStats, Id}; /// An event emitted when finding differences between two trees. #[derive(Debug, Clone, Copy)] @@ -80,25 +67,25 @@ pub enum Event<'a, 'old, 'new> { }, } -impl<'a, 'old, 'new> Event<'a, 'old, 'new> { - /// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification] - /// or one of the entries to compare is not a blob. - pub fn diff( +impl<'a, 'old, 'new> super::Change<'a, 'old, 'new> { + /// Produce a platform for performing a line-diff no matter whether the underlying [Event] is an addition, modification, + /// deletion or rewrite. + /// Use `resource_cache` to store the diffable data and possibly reuse previously stored data. + /// Afterwards the platform, which holds on to `resource_cache`, can be used to perform ready-made operations on the + /// pre-set resources. + /// + /// ### Warning about Memory Consumption + /// + /// `resource_cache` only grows, so one should call [`gix_diff::blob::Platform::clear_resource_cache`] occasionally. + pub fn diff<'b>( &self, - ) -> Option<Result<crate::object::blob::diff::Platform<'old, 'new>, crate::object::blob::diff::init::Error>> { - match self { - Event::Modification { - previous_entry_mode, - previous_id, - entry_mode, - id, - } if entry_mode.is_blob() && previous_entry_mode.is_blob() => { - Some(crate::object::blob::diff::Platform::from_ids(previous_id, id)) - } - _ => None, - } + resource_cache: &'b mut gix_diff::blob::Platform, + ) -> Result<crate::object::blob::diff::Platform<'b>, crate::object::blob::diff::init::Error> { + crate::object::blob::diff::Platform::from_tree_change(self, resource_cache) } +} +impl<'a, 'old, 'new> Event<'a, 'old, 'new> { /// Return the current mode of this instance. pub fn entry_mode(&self) -> gix_object::tree::EntryMode { match self { diff --git a/vendor/gix/src/object/tree/diff/for_each.rs b/vendor/gix/src/object/tree/diff/for_each.rs index 3932f9027..c1b76fbe6 100644 --- a/vendor/gix/src/object/tree/diff/for_each.rs +++ b/vendor/gix/src/object/tree/diff/for_each.rs @@ -1,14 +1,11 @@ use gix_object::TreeRefIter; -use gix_odb::FindExt; use super::{change, Action, Change, Platform}; use crate::{ bstr::BStr, + diff::{rewrites, rewrites::tracker}, ext::ObjectIdExt, - object::tree::{ - diff, - diff::{rewrites, tracked}, - }, + object::tree::diff, Repository, Tree, }; @@ -20,12 +17,10 @@ pub enum Error { Diff(#[from] gix_diff::tree::changes::Error), #[error("The user-provided callback failed")] ForEach(#[source] Box<dyn std::error::Error + Send + Sync + 'static>), - #[error("Could not find blob for similarity checking")] - FindExistingBlob(#[from] crate::object::find::existing::Error), - #[error("Could not configure diff algorithm prior to checking similarity")] - ConfigureDiffAlgorithm(#[from] crate::config::diff::algorithm::Error), - #[error("Could not traverse tree to obtain possible sources for copies")] - TraverseTreeForExhaustiveCopyDetection(#[from] gix_traverse::tree::breadthfirst::Error), + #[error(transparent)] + ResourceCache(#[from] crate::repository::diff::resource_cache::Error), + #[error("Failure during rename tracking")] + RenameTracking(#[from] tracker::emit::Error), } /// @@ -49,24 +44,59 @@ impl<'a, 'old> Platform<'a, 'old> { where E: std::error::Error + Sync + Send + 'static, { + self.for_each_to_obtain_tree_inner(other, for_each, None) + } + + /// Like [`Self::for_each_to_obtain_tree()`], but with a reusable `resource_cache` which is used to perform + /// diffs fast. + /// + /// Reusing it between multiple invocations saves a lot of IOps as it avoids the creation + /// of a temporary `resource_cache` that triggers reading or checking for multiple gitattribute files. + /// Note that it's recommended to call [`gix_diff::blob::Platform::clear_resource_cache()`] between the calls + /// to avoid runaway memory usage, as the cache isn't limited. + /// + /// Note that to do rename tracking like `git` does, one has to configure the `resource_cache` with + /// a conversion pipeline that uses [`gix_diff::blob::pipeline::Mode::ToGit`]. + pub fn for_each_to_obtain_tree_with_cache<'new, E>( + &mut self, + other: &Tree<'new>, + resource_cache: &mut gix_diff::blob::Platform, + for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>, + ) -> Result<Outcome, Error> + where + E: std::error::Error + Sync + Send + 'static, + { + self.for_each_to_obtain_tree_inner(other, for_each, Some(resource_cache)) + } + + fn for_each_to_obtain_tree_inner<'new, E>( + &mut self, + other: &Tree<'new>, + for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>, + resource_cache: Option<&mut gix_diff::blob::Platform>, + ) -> Result<Outcome, Error> + where + E: std::error::Error + Sync + Send + 'static, + { let repo = self.lhs.repo; let mut delegate = Delegate { src_tree: self.lhs, other_repo: other.repo, recorder: gix_diff::tree::Recorder::default().track_location(self.tracking), visit: for_each, - tracked: self.rewrites.map(|r| tracked::State::new(r, self.tracking)), + location: self.tracking, + tracked: self.rewrites.map(rewrites::Tracker::new), err: None, }; match gix_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain( TreeRefIter::from_bytes(&other.data), &mut self.state, - |oid, buf| repo.objects.find_tree_iter(oid, buf), + &repo.objects, &mut delegate, ) { Ok(()) => { let outcome = Outcome { - rewrites: delegate.process_tracked_changes()?, + rewrites: delegate.process_tracked_changes(resource_cache)?, }; match delegate.err { Some(err) => Err(Error::ForEach(Box::new(err))), @@ -88,7 +118,8 @@ struct Delegate<'a, 'old, 'new, VisitFn, E> { other_repo: &'new Repository, recorder: gix_diff::tree::Recorder, visit: VisitFn, - tracked: Option<tracked::State>, + tracked: Option<rewrites::Tracker<gix_diff::tree::visit::Change>>, + location: Option<gix_diff::tree::recorder::Location>, err: Option<E>, } @@ -138,12 +169,25 @@ where } } - fn process_tracked_changes(&mut self) -> Result<Option<rewrites::Outcome>, Error> { + fn process_tracked_changes( + &mut self, + diff_cache: Option<&mut gix_diff::blob::Platform>, + ) -> Result<Option<rewrites::Outcome>, Error> { let tracked = match self.tracked.as_mut() { Some(t) => t, None => return Ok(None), }; + let repo = self.src_tree.repo; + let mut storage; + let diff_cache = match diff_cache { + Some(cache) => cache, + None => { + storage = repo.diff_resource_cache(gix_diff::blob::pipeline::Mode::ToGit, Default::default())?; + &mut storage + } + }; + let outcome = tracked.emit( |dest, source| match source { Some(source) => { @@ -152,14 +196,14 @@ where location: dest.location, event: diff::change::Event::Rewrite { source_location: source.location, - source_entry_mode: source.mode, + source_entry_mode: source.entry_mode, source_id: source.id.attach(self.src_tree.repo), entry_mode: mode, id: oid.to_owned().attach(self.other_repo), diff: source.diff, copy: match source.kind { - tracked::visit::Kind::RenameTarget => false, - tracked::visit::Kind::CopyDestination => true, + tracker::visit::SourceKind::Rename => false, + tracker::visit::SourceKind::Copy => true, }, }, }; @@ -181,7 +225,13 @@ where &mut self.err, ), }, - self.src_tree, + diff_cache, + &self.src_tree.repo.objects, + |push| { + self.src_tree + .traverse() + .breadthfirst(&mut tree_to_changes::Delegate::new(push, self.location)) + }, )?; Ok(Some(outcome)) } @@ -234,3 +284,68 @@ where } } } + +mod tree_to_changes { + use gix_diff::tree::visit::Change; + use gix_object::tree::EntryRef; + + use crate::bstr::BStr; + + pub struct Delegate<'a> { + push: &'a mut dyn FnMut(Change, &BStr), + recorder: gix_traverse::tree::Recorder, + } + + impl<'a> Delegate<'a> { + pub fn new( + push: &'a mut dyn FnMut(Change, &BStr), + location: Option<gix_diff::tree::recorder::Location>, + ) -> Self { + let location = location.map(|t| match t { + gix_diff::tree::recorder::Location::FileName => gix_traverse::tree::recorder::Location::FileName, + gix_diff::tree::recorder::Location::Path => gix_traverse::tree::recorder::Location::Path, + }); + Self { + push, + recorder: gix_traverse::tree::Recorder::default().track_location(location), + } + } + } + + impl gix_traverse::tree::Visit for Delegate<'_> { + fn pop_front_tracked_path_and_set_current(&mut self) { + self.recorder.pop_front_tracked_path_and_set_current() + } + + fn push_back_tracked_path_component(&mut self, component: &BStr) { + self.recorder.push_back_tracked_path_component(component) + } + + fn push_path_component(&mut self, component: &BStr) { + self.recorder.push_path_component(component) + } + + fn pop_path_component(&mut self) { + self.recorder.pop_path_component(); + } + + fn visit_tree(&mut self, _entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action { + gix_traverse::tree::visit::Action::Continue + } + + fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action { + if entry.mode.is_blob() { + (self.push)( + Change::Modification { + previous_entry_mode: entry.mode, + previous_oid: gix_hash::ObjectId::null(entry.oid.kind()), + entry_mode: entry.mode, + oid: entry.oid.to_owned(), + }, + self.recorder.path(), + ); + } + gix_traverse::tree::visit::Action::Continue + } + } +} diff --git a/vendor/gix/src/object/tree/diff/mod.rs b/vendor/gix/src/object/tree/diff/mod.rs index 5f7a041e4..858775610 100644 --- a/vendor/gix/src/object/tree/diff/mod.rs +++ b/vendor/gix/src/object/tree/diff/mod.rs @@ -1,6 +1,6 @@ use gix_diff::tree::recorder::Location; -use crate::{bstr::BStr, Tree}; +use crate::{bstr::BStr, diff::Rewrites, Tree}; /// Returned by the `for_each` function to control flow. #[derive(Default, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] @@ -39,7 +39,7 @@ impl<'repo> Tree<'repo> { /// try to access blobs to compute a similarity metric. Thus, it's more compatible to turn rewrite tracking off /// using [`Platform::track_rewrites()`]. #[allow(clippy::result_large_err)] - pub fn changes<'a>(&'a self) -> Result<Platform<'a, 'repo>, rewrites::Error> { + pub fn changes<'a>(&'a self) -> Result<Platform<'a, 'repo>, crate::diff::new_rewrites::Error> { Ok(Platform { state: Default::default(), lhs: self, @@ -58,34 +58,6 @@ pub struct Platform<'a, 'repo> { rewrites: Option<Rewrites>, } -/// A structure to capture how to perform rename and copy tracking -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Rewrites { - /// If `Some(…)`, do also find copies. `None` is the default which does not try to detect copies at all. - /// - /// Note that this is an even more expensive operation than detecting renames as files. - pub copies: Option<rewrites::Copies>, - /// The percentage of similarity needed for files to be considered renamed, defaulting to `Some(0.5)`. - /// This field is similar to `git diff -M50%`. - /// - /// If `None`, files are only considered equal if their content matches 100%. - /// Note that values greater than 1.0 have no different effect than 1.0. - pub percentage: Option<f32>, - /// The amount of files to consider for fuzzy rename or copy tracking. Defaults to 1000, meaning that only 1000*1000 - /// combinations can be tested for fuzzy matches, i.e. the ones that try to find matches by comparing similarity. - /// If 0, there is no limit. - /// - /// If the limit would not be enough to test the entire set of combinations, the algorithm will trade in precision and not - /// run the fuzzy version of identity tests at all. That way results are never partial. - pub limit: usize, -} - -/// -pub mod rewrites; - -/// types to actually perform rename tracking. -pub(crate) mod tracked; - /// Configuration impl<'a, 'repo> Platform<'a, 'repo> { /// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item. diff --git a/vendor/gix/src/object/tree/diff/rewrites.rs b/vendor/gix/src/object/tree/diff/rewrites.rs deleted file mode 100644 index e434726d9..000000000 --- a/vendor/gix/src/object/tree/diff/rewrites.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::{ - config::{cache::util::ApplyLeniency, tree::Diff}, - diff::rename::Tracking, - object::tree::diff::Rewrites, -}; - -/// From where to source copies -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum CopySource { - /// Find copies from the set of modified files only. - FromSetOfModifiedFiles, - /// Find copies from the set of changed files, as well as all files known to the source (i.e. previous state) of the tree. - /// - /// This can be an expensive operation as it scales exponentially with the total amount of files in the tree. - FromSetOfModifiedFilesAndSourceTree, -} - -/// How to determine copied files. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Copies { - /// The set of files to search when finding the source of copies. - pub source: CopySource, - /// Equivalent to [`Rewrites::percentage`], but used for copy tracking. - /// - /// Useful to have similarity-based rename tracking and cheaper copy tracking, which also is the default - /// as only identity plays a role. - pub percentage: Option<f32>, -} - -impl Default for Copies { - fn default() -> Self { - Copies { - source: CopySource::FromSetOfModifiedFiles, - percentage: Some(0.5), - } - } -} - -/// Information collected while handling rewrites of files which may be tracked. -#[derive(Default, Clone, Copy, Debug, PartialEq)] -pub struct Outcome { - /// The options used to guide the rewrite tracking. Either fully provided by the caller or retrieved from git configuration. - pub options: Rewrites, - /// The amount of similarity checks that have been conducted to find renamed files and potentially copies. - pub num_similarity_checks: usize, - /// Set to the amount of worst-case rename permutations we didn't search as our limit didn't allow it. - pub num_similarity_checks_skipped_for_rename_tracking_due_to_limit: usize, - /// Set to the amount of worst-case copy permutations we didn't search as our limit didn't allow it. - pub num_similarity_checks_skipped_for_copy_tracking_due_to_limit: usize, -} - -/// The error returned by [`Rewrites::try_from_config()`]. -#[derive(Debug, thiserror::Error)] -#[allow(missing_docs)] -pub enum Error { - #[error(transparent)] - ConfigDiffRenames(#[from] crate::config::key::GenericError), - #[error(transparent)] - ConfigDiffRenameLimit(#[from] crate::config::unsigned_integer::Error), -} - -/// The default settings for rewrites according to the git configuration defaults. -impl Default for Rewrites { - fn default() -> Self { - Rewrites { - copies: None, - percentage: Some(0.5), - limit: 1000, - } - } -} - -impl Rewrites { - /// Create an instance by reading all relevant information from the `config`uration, while being `lenient` or not. - /// Returns `Ok(None)` if nothing is configured. - /// - /// Note that missing values will be defaulted similar to what git does. - #[allow(clippy::result_large_err)] - pub fn try_from_config(config: &gix_config::File<'static>, lenient: bool) -> Result<Option<Self>, Error> { - let key = "diff.renames"; - let copies = match config - .boolean_by_key(key) - .map(|value| Diff::RENAMES.try_into_renames(value)) - .transpose() - .with_leniency(lenient)? - { - Some(renames) => match renames { - Tracking::Disabled => return Ok(None), - Tracking::Renames => None, - Tracking::RenamesAndCopies => Some(Copies::default()), - }, - None => return Ok(None), - }; - - let default = Self::default(); - Ok(Rewrites { - copies, - limit: config - .integer_by_key("diff.renameLimit") - .map(|value| Diff::RENAME_LIMIT.try_into_usize(value)) - .transpose() - .with_leniency(lenient)? - .unwrap_or(default.limit), - ..default - } - .into()) - } -} diff --git a/vendor/gix/src/object/tree/diff/tracked.rs b/vendor/gix/src/object/tree/diff/tracked.rs deleted file mode 100644 index d6042fcbc..000000000 --- a/vendor/gix/src/object/tree/diff/tracked.rs +++ /dev/null @@ -1,490 +0,0 @@ -use std::ops::Range; - -use gix_diff::tree::visit::Change; -use gix_object::tree::EntryMode; - -use crate::{ - bstr::BStr, - ext::ObjectIdExt, - object::tree::diff::{ - change::DiffLineStats, - rewrites::{CopySource, Outcome}, - Rewrites, - }, - Repository, Tree, -}; - -/// A set of tracked items allows to figure out their relations by figuring out their similarity. -pub struct Item { - /// The underlying raw change - change: Change, - /// That slice into the backing for paths. - location: Range<usize>, - /// If true, this item was already emitted, i.e. seen by the caller. - emitted: bool, -} - -impl Item { - fn location<'a>(&self, backing: &'a [u8]) -> &'a BStr { - backing[self.location.clone()].as_ref() - } - fn entry_mode_compatible(&self, mode: EntryMode) -> bool { - use EntryMode::*; - matches!( - (mode, self.change.entry_mode()), - (Blob | BlobExecutable, Blob | BlobExecutable) | (Link, Link) - ) - } - - fn is_source_for_destination_of(&self, kind: visit::Kind, dest_item_mode: EntryMode) -> bool { - self.entry_mode_compatible(dest_item_mode) - && match kind { - visit::Kind::RenameTarget => !self.emitted && matches!(self.change, Change::Deletion { .. }), - visit::Kind::CopyDestination => { - matches!(self.change, Change::Modification { .. }) - } - } - } -} - -pub struct State { - items: Vec<Item>, - path_backing: Vec<u8>, - rewrites: Rewrites, - tracking: Option<gix_diff::tree::recorder::Location>, -} - -pub mod visit { - use crate::{bstr::BStr, object::tree::diff::change::DiffLineStats}; - - pub struct Source<'a> { - pub mode: gix_object::tree::EntryMode, - pub id: gix_hash::ObjectId, - pub kind: Kind, - pub location: &'a BStr, - pub diff: Option<DiffLineStats>, - } - - #[derive(Debug, Copy, Clone, Eq, PartialEq)] - pub enum Kind { - RenameTarget, - CopyDestination, - } - - pub struct Destination<'a> { - pub change: gix_diff::tree::visit::Change, - pub location: &'a BStr, - } -} - -impl State { - pub(crate) fn new(renames: Rewrites, tracking: Option<gix_diff::tree::recorder::Location>) -> Self { - State { - items: vec![], - path_backing: vec![], - rewrites: renames, - tracking, - } - } -} - -/// build state and find matches. -impl State { - /// We may refuse the push if that information isn't needed for what we have to track. - pub fn try_push_change(&mut self, change: Change, location: &BStr) -> Option<Change> { - if !change.entry_mode().is_blob_or_symlink() { - return Some(change); - } - let keep = match (self.rewrites.copies, &change) { - (Some(_find_copies), _) => true, - (None, Change::Modification { .. }) => false, - (None, _) => true, - }; - - if !keep { - return Some(change); - } - - let start = self.path_backing.len(); - self.path_backing.extend_from_slice(location); - self.items.push(Item { - location: start..self.path_backing.len(), - change, - emitted: false, - }); - None - } - - /// Can only be called once effectively as it alters its own state. - /// - /// `cb(destination, source)` is called for each item, either with `Some(source)` if it's - /// the destination of a copy or rename, or with `None` for source if no relation to other - /// items in the tracked set exist. - pub fn emit( - &mut self, - mut cb: impl FnMut(visit::Destination<'_>, Option<visit::Source<'_>>) -> gix_diff::tree::visit::Action, - src_tree: &Tree<'_>, - ) -> Result<Outcome, crate::object::tree::diff::for_each::Error> { - fn by_id_and_location(a: &Item, b: &Item) -> std::cmp::Ordering { - a.change.oid().cmp(b.change.oid()).then_with(|| { - a.location - .start - .cmp(&b.location.start) - .then(a.location.end.cmp(&b.location.end)) - }) - } - self.items.sort_by(by_id_and_location); - - let mut out = Outcome { - options: self.rewrites, - ..Default::default() - }; - out = self.match_pairs_of_kind( - visit::Kind::RenameTarget, - &mut cb, - self.rewrites.percentage, - out, - src_tree.repo, - )?; - - if let Some(copies) = self.rewrites.copies { - out = self.match_pairs_of_kind( - visit::Kind::CopyDestination, - &mut cb, - copies.percentage, - out, - src_tree.repo, - )?; - - match copies.source { - CopySource::FromSetOfModifiedFiles => {} - CopySource::FromSetOfModifiedFilesAndSourceTree => { - src_tree - .traverse() - .breadthfirst(&mut tree_to_events::Delegate::new(self))?; - self.items.sort_by(by_id_and_location); - - out = self.match_pairs_of_kind( - visit::Kind::CopyDestination, - &mut cb, - copies.percentage, - out, - src_tree.repo, - )?; - } - } - } - - self.items - .sort_by(|a, b| a.location(&self.path_backing).cmp(b.location(&self.path_backing))); - for item in self.items.drain(..).filter(|item| !item.emitted) { - if cb( - visit::Destination { - location: item.location(&self.path_backing), - change: item.change, - }, - None, - ) == gix_diff::tree::visit::Action::Cancel - { - break; - } - } - Ok(out) - } - - fn match_pairs_of_kind( - &mut self, - kind: visit::Kind, - cb: &mut impl FnMut(visit::Destination<'_>, Option<visit::Source<'_>>) -> gix_diff::tree::visit::Action, - percentage: Option<f32>, - mut out: Outcome, - repo: &Repository, - ) -> Result<Outcome, crate::object::tree::diff::for_each::Error> { - // we try to cheaply reduce the set of possibilities first, before possibly looking more exhaustively. - let needs_second_pass = !needs_exact_match(percentage); - if self.match_pairs(cb, None /* by identity */, kind, repo, &mut out)? == gix_diff::tree::visit::Action::Cancel - { - return Ok(out); - } - if needs_second_pass { - let is_limited = if self.rewrites.limit == 0 { - false - } else if let Some(permutations) = permutations_over_limit(&self.items, self.rewrites.limit, kind) { - match kind { - visit::Kind::RenameTarget => { - out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit = permutations; - } - visit::Kind::CopyDestination => { - out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit = permutations; - } - } - true - } else { - false - }; - if !is_limited { - self.match_pairs(cb, self.rewrites.percentage, kind, repo, &mut out)?; - } - } - Ok(out) - } - - fn match_pairs( - &mut self, - cb: &mut impl FnMut(visit::Destination<'_>, Option<visit::Source<'_>>) -> gix_diff::tree::visit::Action, - percentage: Option<f32>, - kind: visit::Kind, - repo: &Repository, - stats: &mut Outcome, - ) -> Result<gix_diff::tree::visit::Action, crate::object::tree::diff::for_each::Error> { - // TODO(perf): reuse object data and interner state and interned tokens, make these available to `find_match()` - let mut dest_ofs = 0; - while let Some((mut dest_idx, dest)) = self.items[dest_ofs..].iter().enumerate().find_map(|(idx, item)| { - (!item.emitted && matches!(item.change, Change::Addition { .. })).then_some((idx, item)) - }) { - dest_idx += dest_ofs; - dest_ofs = dest_idx + 1; - let src = - find_match(&self.items, dest, dest_idx, percentage, kind, repo, stats)?.map(|(src_idx, src, diff)| { - let (id, mode) = src.change.oid_and_entry_mode(); - let id = id.to_owned(); - let location = src.location(&self.path_backing); - ( - visit::Source { - mode, - id, - kind, - location, - diff, - }, - src_idx, - ) - }); - if src.is_none() { - continue; - } - let location = dest.location(&self.path_backing); - let change = dest.change.clone(); - let dest = visit::Destination { change, location }; - self.items[dest_idx].emitted = true; - if let Some(src_idx) = src.as_ref().map(|t| t.1) { - self.items[src_idx].emitted = true; - } - if cb(dest, src.map(|t| t.0)) == gix_diff::tree::visit::Action::Cancel { - return Ok(gix_diff::tree::visit::Action::Cancel); - } - } - Ok(gix_diff::tree::visit::Action::Continue) - } -} - -fn permutations_over_limit(items: &[Item], limit: usize, kind: visit::Kind) -> Option<usize> { - let (sources, destinations) = items - .iter() - .filter(|item| match kind { - visit::Kind::RenameTarget => !item.emitted, - visit::Kind::CopyDestination => true, - }) - .fold((0, 0), |(mut src, mut dest), item| { - match item.change { - Change::Addition { .. } => { - dest += 1; - } - Change::Deletion { .. } => { - if kind == visit::Kind::RenameTarget { - src += 1 - } - } - Change::Modification { .. } => { - if kind == visit::Kind::CopyDestination { - src += 1 - } - } - } - (src, dest) - }); - let permutations = sources * destinations; - (permutations > limit * limit).then_some(permutations) -} - -fn needs_exact_match(percentage: Option<f32>) -> bool { - percentage.map_or(true, |p| p >= 1.0) -} - -/// <`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`. -/// The latter can be `None` or `Some(x)` where `x>=1` for identity, and anything else for similarity. -/// We also ignore emitted items entirely. -/// Use `kind` to indicate what kind of match we are looking for, which might be deletions matching an `item` addition, or -/// any non-deletion otherwise. -/// Note that we always try to find by identity first even if a percentage is given as it's much faster and may reduce the set -/// of items to be searched. -fn find_match<'a>( - items: &'a [Item], - item: &Item, - item_idx: usize, - percentage: Option<f32>, - kind: visit::Kind, - repo: &Repository, - stats: &mut Outcome, -) -> Result<Option<SourceTuple<'a>>, crate::object::tree::diff::for_each::Error> { - let (item_id, item_mode) = item.change.oid_and_entry_mode(); - if needs_exact_match(percentage) || item_mode == gix_object::tree::EntryMode::Link { - let first_idx = items.partition_point(|a| a.change.oid() < item_id); - let range = match items.get(first_idx..).map(|items| { - let end = items - .iter() - .position(|a| a.change.oid() != item_id) - .map_or(items.len(), |idx| first_idx + idx); - first_idx..end - }) { - Some(range) => range, - None => return Ok(None), - }; - if range.is_empty() { - return Ok(None); - } - let res = items[range.clone()].iter().enumerate().find_map(|(mut src_idx, src)| { - src_idx += range.start; - (src_idx != item_idx && src.is_source_for_destination_of(kind, item_mode)).then_some((src_idx, src, None)) - }); - if let Some(src) = res { - return Ok(Some(src)); - } - } else { - let new = item_id.to_owned().attach(repo).object()?; - let percentage = percentage.expect("it's set to something below 1.0 and we assured this"); - debug_assert!( - item.change.entry_mode().is_blob(), - "symlinks are matched exactly, and trees aren't used here" - ); - let algo = repo.config.diff_algorithm()?; - for (can_idx, src) in items - .iter() - .enumerate() - .filter(|(src_idx, src)| *src_idx != item_idx && src.is_source_for_destination_of(kind, item_mode)) - { - let old = src.change.oid().to_owned().attach(repo).object()?; - // TODO: make sure we get attribute handling and binary skips and filters right here. There is crate::object::blob::diff::Platform - // which should have facilities for that one day, but we don't use it because we need newlines in our tokens. - let tokens = gix_diff::blob::intern::InternedInput::new( - gix_diff::blob::sources::byte_lines_with_terminator(&old.data), - gix_diff::blob::sources::byte_lines_with_terminator(&new.data), - ); - let counts = gix_diff::blob::diff( - algo, - &tokens, - gix_diff::blob::sink::Counter::new(diff::Statistics { - removed_bytes: 0, - input: &tokens, - }), - ); - let similarity = (old.data.len() - counts.wrapped) as f32 / old.data.len().max(new.data.len()) as f32; - stats.num_similarity_checks += 1; - if similarity >= percentage { - return Ok(Some(( - can_idx, - src, - DiffLineStats { - removals: counts.removals, - insertions: counts.insertions, - before: tokens.before.len().try_into().expect("interner handles only u32"), - after: tokens.after.len().try_into().expect("interner handles only u32"), - } - .into(), - ))); - } - } - } - Ok(None) -} - -mod diff { - use std::ops::Range; - - pub struct Statistics<'a, 'data> { - pub removed_bytes: usize, - pub input: &'a gix_diff::blob::intern::InternedInput<&'data [u8]>, - } - - impl<'a, 'data> gix_diff::blob::Sink for Statistics<'a, 'data> { - type Out = usize; - - fn process_change(&mut self, before: Range<u32>, _after: Range<u32>) { - self.removed_bytes = self.input.before[before.start as usize..before.end as usize] - .iter() - .map(|token| self.input.interner[*token].len()) - .sum(); - } - - fn finish(self) -> Self::Out { - self.removed_bytes - } - } -} - -mod tree_to_events { - use gix_diff::tree::visit::Change; - use gix_object::tree::EntryRef; - - use crate::bstr::BStr; - - pub struct Delegate<'a> { - parent: &'a mut super::State, - recorder: gix_traverse::tree::Recorder, - } - - impl<'a> Delegate<'a> { - pub fn new(parent: &'a mut super::State) -> Self { - let tracking = parent.tracking.map(|t| match t { - gix_diff::tree::recorder::Location::FileName => gix_traverse::tree::recorder::Location::FileName, - gix_diff::tree::recorder::Location::Path => gix_traverse::tree::recorder::Location::Path, - }); - Self { - parent, - recorder: gix_traverse::tree::Recorder::default().track_location(tracking), - } - } - } - - impl gix_traverse::tree::Visit for Delegate<'_> { - fn pop_front_tracked_path_and_set_current(&mut self) { - self.recorder.pop_front_tracked_path_and_set_current() - } - - fn push_back_tracked_path_component(&mut self, component: &BStr) { - self.recorder.push_back_tracked_path_component(component) - } - - fn push_path_component(&mut self, component: &BStr) { - self.recorder.push_path_component(component) - } - - fn pop_path_component(&mut self) { - self.recorder.pop_path_component(); - } - - fn visit_tree(&mut self, _entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action { - gix_traverse::tree::visit::Action::Continue - } - - fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action { - if entry.mode.is_blob() { - self.parent.try_push_change( - Change::Modification { - previous_entry_mode: entry.mode, - previous_oid: gix_hash::ObjectId::null(entry.oid.kind()), - entry_mode: entry.mode, - oid: entry.oid.to_owned(), - }, - self.recorder.path(), - ); - // make sure these aren't viable to be emitted anymore. - self.parent.items.last_mut().expect("just pushed").emitted = true; - } - gix_traverse::tree::visit::Action::Continue - } - } -} diff --git a/vendor/gix/src/object/tree/iter.rs b/vendor/gix/src/object/tree/iter.rs index 848d9eeab..cc9c4bd28 100644 --- a/vendor/gix/src/object/tree/iter.rs +++ b/vendor/gix/src/object/tree/iter.rs @@ -51,7 +51,7 @@ impl<'repo, 'a> std::fmt::Display for EntryRef<'repo, 'a> { write!( f, "{:06o} {:>6} {}\t{}", - self.mode() as u32, + *self.mode(), self.mode().as_str(), self.id().shorten_or_id(), self.filename() diff --git a/vendor/gix/src/object/tree/mod.rs b/vendor/gix/src/object/tree/mod.rs index e4dac24f8..a8173fce4 100644 --- a/vendor/gix/src/object/tree/mod.rs +++ b/vendor/gix/src/object/tree/mod.rs @@ -1,8 +1,7 @@ use gix_hash::ObjectId; use gix_macros::momo; -pub use gix_object::tree::EntryMode; -use gix_object::{bstr::BStr, TreeRefIter}; -use gix_odb::FindExt; +pub use gix_object::tree::{EntryKind, EntryMode}; +use gix_object::{bstr::BStr, FindExt, TreeRefIter}; use crate::{object::find, Id, ObjectDetached, Tree}; diff --git a/vendor/gix/src/object/tree/traverse.rs b/vendor/gix/src/object/tree/traverse.rs index 974df6b0d..f2f3ff817 100644 --- a/vendor/gix/src/object/tree/traverse.rs +++ b/vendor/gix/src/object/tree/traverse.rs @@ -1,5 +1,3 @@ -use gix_odb::FindExt; - use crate::Tree; /// Traversal @@ -52,11 +50,6 @@ impl<'a, 'repo> Platform<'a, 'repo> { { let root = gix_object::TreeRefIter::from_bytes(&self.root.data); let state = gix_traverse::tree::breadthfirst::State::default(); - gix_traverse::tree::breadthfirst( - root, - state, - |oid, buf| self.root.repo.objects.find_tree_iter(oid, buf).ok(), - delegate, - ) + gix_traverse::tree::breadthfirst(root, state, &self.root.repo.objects, delegate) } } diff --git a/vendor/gix/src/open/repository.rs b/vendor/gix/src/open/repository.rs index fde647a4e..6c3f07f42 100644 --- a/vendor/gix/src/open/repository.rs +++ b/vendor/gix/src/open/repository.rs @@ -8,7 +8,7 @@ use super::{Error, Options}; use crate::{ config, config::{ - cache::{interpolate_context, util::ApplyLeniency}, + cache::interpolate_context, tree::{gitoxide, Core, Key, Safe}, }, open::Permissions, @@ -237,13 +237,27 @@ impl ThreadSafeRepository { .resolved .path_filter("core", None, Core::WORKTREE.name, &mut filter_config_section) { + let wt_clone = wt.clone(); let wt_path = wt .interpolate(interpolate_context(git_install_dir.as_deref(), home.as_deref())) - .map_err(config::Error::PathInterpolation)?; - worktree_dir = { - gix_path::normalize(git_dir.join(wt_path).into(), current_dir) - .and_then(|wt| wt.as_ref().is_dir().then(|| wt.into_owned())) + .map_err(|err| config::Error::PathInterpolation { + path: wt_clone.value.into_owned(), + source: err, + })?; + worktree_dir = gix_path::normalize(git_dir.join(wt_path).into(), current_dir).map(Cow::into_owned); + #[allow(unused_variables)] + if let Some(worktree_path) = worktree_dir.as_deref().filter(|wtd| !wtd.is_dir()) { + gix_trace::warn!("The configured worktree path '{}' is not a directory or doesn't exist - `core.worktree` may be misleading", worktree_path.display()); } + } else if !config.lenient_config + && config + .resolved + .boolean_filter("core", None, Core::WORKTREE.name, &mut filter_config_section) + .is_some() + { + return Err(Error::from(config::Error::ConfigTypedString( + config::key::GenericErrorWithValue::from(&Core::WORKTREE), + ))); } } @@ -259,6 +273,7 @@ impl ThreadSafeRepository { } refs.write_reflog = config::cache::util::reflog_or_default(config.reflog, worktree_dir.is_some()); + refs.namespace = config.refs_namespace.clone(); let replacements = replacement_objects_refs_prefix(&config.resolved, lenient_config, filter_config_section)? .and_then(|prefix| { let _span = gix_trace::detail!("find replacement objects"); @@ -310,11 +325,7 @@ fn replacement_objects_refs_prefix( lenient: bool, mut filter_config_section: fn(&gix_config::file::Metadata) -> bool, ) -> Result<Option<PathBuf>, Error> { - let is_disabled = config - .boolean_filter_by_key("core.useReplaceRefs", &mut filter_config_section) - .map(|b| Core::USE_REPLACE_REFS.enrich_error(b)) - .transpose() - .with_leniency(lenient) + let is_disabled = config::shared::is_replace_refs_enabled(config, lenient, filter_config_section) .map_err(config::Error::ConfigBoolean)? .unwrap_or(true); diff --git a/vendor/gix/src/pathspec.rs b/vendor/gix/src/pathspec.rs index 235a91d76..a56ad1c32 100644 --- a/vendor/gix/src/pathspec.rs +++ b/vendor/gix/src/pathspec.rs @@ -1,6 +1,5 @@ //! Pathspec plumbing and abstractions use gix_macros::momo; -use gix_odb::FindExt; pub use gix_pathspec::*; use crate::{bstr::BStr, AttributeStack, Pathspec, PathspecDetached, Repository}; @@ -122,9 +121,7 @@ impl<'repo> Pathspec<'repo> { let stack = self.stack.as_mut().expect("initialized in advance"); stack .set_case(case) - .at_entry(relative_path, Some(is_dir), |id, buf| { - self.repo.objects.find_blob(id, buf) - }) + .at_entry(relative_path, Some(is_dir), &self.repo.objects) .map_or(false, |platform| platform.matching_attributes(out)) }, ) @@ -180,7 +177,7 @@ impl PathspecDetached { let stack = self.stack.as_mut().expect("initialized in advance"); stack .set_case(case) - .at_entry(relative_path, Some(is_dir), |id, buf| self.odb.find_blob(id, buf)) + .at_entry(relative_path, Some(is_dir), &self.odb) .map_or(false, |platform| platform.matching_attributes(out)) }, ) diff --git a/vendor/gix/src/prelude.rs b/vendor/gix/src/prelude.rs index 36fbfc7b1..8a3e01124 100644 --- a/vendor/gix/src/prelude.rs +++ b/vendor/gix/src/prelude.rs @@ -1,4 +1,5 @@ pub use gix_features::parallel::reduce::Finalize; -pub use gix_odb::{Find, FindExt, Header, HeaderExt, Write}; +pub use gix_object::{Find, FindExt}; +pub use gix_odb::{Header, HeaderExt, Write}; pub use crate::ext::*; diff --git a/vendor/gix/src/reference/edits.rs b/vendor/gix/src/reference/edits.rs index 208340770..cba652630 100644 --- a/vendor/gix/src/reference/edits.rs +++ b/vendor/gix/src/reference/edits.rs @@ -1,9 +1,10 @@ /// pub mod set_target_id { - use crate::{bstr::BString, Reference}; use gix_macros::momo; use gix_ref::{transaction::PreviousValue, Target}; + use crate::{bstr::BString, Reference}; + mod error { use gix_ref::FullName; diff --git a/vendor/gix/src/reference/errors.rs b/vendor/gix/src/reference/errors.rs index ff3802e94..d5b09f78e 100644 --- a/vendor/gix/src/reference/errors.rs +++ b/vendor/gix/src/reference/errors.rs @@ -43,9 +43,7 @@ pub mod head_id { #[error(transparent)] Head(#[from] crate::reference::find::existing::Error), #[error(transparent)] - PeelToId(#[from] crate::head::peel::Error), - #[error("Branch '{name}' does not have any commits")] - Unborn { name: gix_ref::FullName }, + PeelToId(#[from] crate::head::peel::into_id::Error), } } @@ -69,9 +67,7 @@ pub mod head_tree_id { #[allow(missing_docs)] pub enum Error { #[error(transparent)] - Head(#[from] crate::reference::find::existing::Error), - #[error(transparent)] - PeelToCommit(#[from] crate::head::peel::to_commit::Error), + HeadCommit(#[from] crate::reference::head_commit::Error), #[error(transparent)] DecodeCommit(#[from] gix_object::decode::Error), } diff --git a/vendor/gix/src/reference/iter.rs b/vendor/gix/src/reference/iter.rs index a79a74743..604a8ac4b 100644 --- a/vendor/gix/src/reference/iter.rs +++ b/vendor/gix/src/reference/iter.rs @@ -2,7 +2,6 @@ use std::path::Path; use gix_macros::momo; -use gix_odb::pack::Find; use gix_ref::file::ReferenceExt; /// A platform to create iterators over references. @@ -96,15 +95,10 @@ impl<'r> Iterator for Iter<'r> { res.map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>) .and_then(|mut r| { if self.peel { - let handle = &self.repo; - r.peel_to_id_in_place(&handle.refs, &mut |oid, buf| { - handle - .objects - .try_find(oid.as_ref(), buf) - .map(|po| po.map(|(o, _l)| (o.kind, o.data))) - }) - .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>) - .map(|_| r) + let repo = &self.repo; + r.peel_to_id_in_place(&repo.refs, &repo.objects) + .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>) + .map(|_| r) } else { Ok(r) } diff --git a/vendor/gix/src/reference/mod.rs b/vendor/gix/src/reference/mod.rs index 2d32f595d..ebdbf66a7 100644 --- a/vendor/gix/src/reference/mod.rs +++ b/vendor/gix/src/reference/mod.rs @@ -1,6 +1,5 @@ //! -use gix_odb::pack::Find; use gix_ref::file::ReferenceExt; use crate::{Id, Reference}; @@ -69,13 +68,8 @@ impl<'repo> Reference<'repo> { /// /// This is useful to learn where this reference is ultimately pointing to. pub fn peel_to_id_in_place(&mut self) -> Result<Id<'repo>, peel::Error> { - let repo = &self.repo; - let oid = self.inner.peel_to_id_in_place(&repo.refs, &mut |oid, buf| { - repo.objects - .try_find(&oid, buf) - .map(|po| po.map(|(o, _l)| (o.kind, o.data))) - })?; - Ok(Id::from_id(oid, repo)) + let oid = self.inner.peel_to_id_in_place(&self.repo.refs, &self.repo.objects)?; + Ok(Id::from_id(oid, self.repo)) } /// Similar to [`peel_to_id_in_place()`][Reference::peel_to_id_in_place()], but consumes this instance. diff --git a/vendor/gix/src/remote/connect.rs b/vendor/gix/src/remote/connect.rs index 6acc9f67f..df2b36230 100644 --- a/vendor/gix/src/remote/connect.rs +++ b/vendor/gix/src/remote/connect.rs @@ -1,8 +1,9 @@ #![allow(clippy::result_large_err)] -use gix_protocol::transport::client::Transport; use std::borrow::Cow; +use gix_protocol::transport::client::Transport; + use crate::{remote::Connection, Remote}; mod error { @@ -57,11 +58,13 @@ impl<'repo> Remote<'repo> { where T: Transport, { + let trace = self.repo.config.trace_packet(); Connection { remote: self, authenticate: None, transport_options: None, transport, + trace, } } @@ -91,6 +94,7 @@ impl<'repo> Remote<'repo> { .then(|| self.repo.ssh_connect_options()) .transpose()? .unwrap_or_default(), + trace: self.repo.config.trace_packet(), }, ) .await?; diff --git a/vendor/gix/src/remote/connection/fetch/mod.rs b/vendor/gix/src/remote/connection/fetch/mod.rs index 8327d5abc..d4afd1023 100644 --- a/vendor/gix/src/remote/connection/fetch/mod.rs +++ b/vendor/gix/src/remote/connection/fetch/mod.rs @@ -281,12 +281,13 @@ where // connection in an async context. gix_protocol::futures_lite::future::block_on(gix_protocol::indicate_end_of_interaction( &mut con.transport, + con.trace, )) .ok(); } #[cfg(not(feature = "async-network-client"))] { - gix_protocol::indicate_end_of_interaction(&mut con.transport).ok(); + gix_protocol::indicate_end_of_interaction(&mut con.transport, con.trace).ok(); } } } diff --git a/vendor/gix/src/remote/connection/fetch/negotiate.rs b/vendor/gix/src/remote/connection/fetch/negotiate.rs index 92a141f6f..f5b6a031c 100644 --- a/vendor/gix/src/remote/connection/fetch/negotiate.rs +++ b/vendor/gix/src/remote/connection/fetch/negotiate.rs @@ -16,7 +16,7 @@ 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_revwalk::graph::lookup::commit::Error), + LookupCommitInGraph(#[from] gix_revwalk::graph::try_lookup_or_insert_default::Error), #[error(transparent)] InitRefsIterator(#[from] crate::reference::iter::init::Error), #[error(transparent)] diff --git a/vendor/gix/src/remote/connection/fetch/receive_pack.rs b/vendor/gix/src/remote/connection/fetch/receive_pack.rs index 18e5ac159..7634b34cf 100644 --- a/vendor/gix/src/remote/connection/fetch/receive_pack.rs +++ b/vendor/gix/src/remote/connection/fetch/receive_pack.rs @@ -3,7 +3,7 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, }; -use gix_odb::{store::RefreshMode, FindExt}; +use gix_odb::store::RefreshMode; use gix_protocol::{ fetch::Arguments, transport::{client::Transport, packetline::read::ProgressAction}, @@ -104,7 +104,7 @@ where gix_protocol::fetch::Response::check_required_features(protocol_version, &fetch_features)?; let sideband_all = fetch_features.iter().any(|(n, _)| *n == "sideband-all"); - let mut arguments = gix_protocol::fetch::Arguments::new(protocol_version, fetch_features); + let mut arguments = gix_protocol::fetch::Arguments::new(protocol_version, fetch_features, con.trace); if matches!(con.remote.fetch_tags, crate::remote::fetch::Tags::Included) { if !arguments.can_use_include_tag() { return Err(Error::MissingServerFeature { @@ -125,7 +125,10 @@ where }); } - let negotiate_span = gix_trace::detail!("negotiate"); + let negotiate_span = gix_trace::detail!( + "negotiate", + protocol_version = self.ref_map.handshake.server_protocol_version as usize + ); let mut negotiator = repo .config .resolved @@ -155,7 +158,9 @@ where let mut previous_response = None::<gix_protocol::fetch::Response>; let (mut write_pack_bundle, negotiate) = match &action { negotiate::Action::NoChange | negotiate::Action::SkipToRefUpdate => { - gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); + gix_protocol::indicate_end_of_interaction(&mut con.transport, con.trace) + .await + .ok(); (None, None) } negotiate::Action::MustNegotiate { @@ -206,7 +211,9 @@ where is_done } Err(err) => { - gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); + gix_protocol::indicate_end_of_interaction(&mut con.transport, con.trace) + .await + .ok(); return Err(err.into()); } }; @@ -214,8 +221,13 @@ where 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 response = gix_protocol::fetch::Response::from_line_reader( + protocol_version, + &mut reader, + is_done, + !is_done, + ) + .await?; let has_pack = response.has_pack(); previous_response = Some(response); if has_pack { @@ -265,14 +277,23 @@ where should_interrupt, Some(Box::new({ let repo = repo.clone(); - move |oid, buf| repo.objects.find(&oid, buf).ok() + repo.objects })), options, )?; + // Assure the final flush packet is consumed. + #[cfg(feature = "async-network-client")] + let has_read_to_end = { rd.get_ref().stopped_at().is_some() }; + #[cfg(not(feature = "async-network-client"))] + let has_read_to_end = { rd.stopped_at().is_some() }; + if !has_read_to_end { + std::io::copy(&mut rd, &mut std::io::sink()).unwrap(); + } #[cfg(feature = "async-network-client")] { reader = rd.into_inner(); } + #[cfg(not(feature = "async-network-client"))] { reader = rd; @@ -284,7 +305,9 @@ where drop(reader); if matches!(protocol_version, gix_protocol::transport::Protocol::V2) { - gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); + gix_protocol::indicate_end_of_interaction(&mut con.transport, con.trace) + .await + .ok(); } if let Some(shallow_lock) = shallow_lock { @@ -387,24 +410,14 @@ fn add_shallow_args( Ok((shallow_commits, shallow_lock)) } -fn setup_remote_progress( +fn setup_remote_progress<'a>( progress: &mut dyn crate::DynNestedProgress, - reader: &mut Box<dyn gix_protocol::transport::client::ExtendedBufRead + Unpin + '_>, - should_interrupt: &AtomicBool, + reader: &mut Box<dyn gix_protocol::transport::client::ExtendedBufRead<'a> + Unpin + 'a>, + should_interrupt: &'a AtomicBool, ) { use gix_protocol::transport::client::ExtendedBufRead; reader.set_progress_handler(Some(Box::new({ let mut remote_progress = progress.add_child_with_id("remote".to_string(), 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); if should_interrupt.load(Ordering::Relaxed) { @@ -413,5 +426,5 @@ fn setup_remote_progress( ProgressAction::Continue } } - }) as gix_protocol::transport::client::HandleProgress)); + }) as gix_protocol::transport::client::HandleProgress<'a>)); } diff --git a/vendor/gix/src/remote/connection/fetch/update_refs/mod.rs b/vendor/gix/src/remote/connection/fetch/update_refs/mod.rs index 3d6fb18bd..c487e7f5c 100644 --- a/vendor/gix/src/remote/connection/fetch/update_refs/mod.rs +++ b/vendor/gix/src/remote/connection/fetch/update_refs/mod.rs @@ -1,7 +1,7 @@ #![allow(clippy::result_large_err)] use std::{collections::BTreeMap, convert::TryInto, path::PathBuf}; -use gix_odb::{Find, FindExt}; +use gix_object::Exists; use gix_ref::{ transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog}, Target, TargetRef, @@ -96,8 +96,8 @@ pub(crate) fn update( ) { // `None` only if unborn. let remote_id = remote.as_id(); - if matches!(dry_run, fetch::DryRun::No) && !remote_id.map_or(true, |id| repo.objects.contains(id)) { - if let Some(remote_id) = remote_id.filter(|id| !repo.objects.contains(id)) { + if matches!(dry_run, fetch::DryRun::No) && !remote_id.map_or(true, |id| repo.objects.exists(id)) { + if let Some(remote_id) = remote_id.filter(|id| !repo.objects.exists(id)) { let update = if is_implicit_tag { Mode::ImplicitTagNotSentByRemote.into() } else { @@ -159,7 +159,7 @@ pub(crate) fn update( }).and_then(|local_commit_time| remote_id .to_owned() - .ancestors(|id, buf| repo.objects.find_commit_iter(id, buf)) + .ancestors(&repo.objects) .sorting( gix_traverse::commit::Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds: local_commit_time @@ -325,11 +325,7 @@ pub(crate) fn update( .packed_refs( match write_packed_refs { fetch::WritePackedRefs::Only => { - gix_ref::file::transaction::PackedRefs::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(Box::new(|oid, buf| { - repo.objects - .try_find(&oid, buf) - .map(|obj| obj.map(|obj| obj.kind)) - }))}, + gix_ref::file::transaction::PackedRefs::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(Box::new(&repo.objects))}, fetch::WritePackedRefs::Never => gix_ref::file::transaction::PackedRefs::DeletionsOnly } ) 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 0b29f14f4..27501720d 100644 --- a/vendor/gix/src/remote/connection/fetch/update_refs/tests.rs +++ b/vendor/gix/src/remote/connection/fetch/update_refs/tests.rs @@ -191,7 +191,7 @@ mod update { #[test] fn checked_out_branches_in_worktrees_are_rejected_with_additional_information() -> Result { - let root = gix_path::realpath(&gix_testtools::scripted_fixture_read_only_with_args( + let root = gix_path::realpath(gix_testtools::scripted_fixture_read_only_with_args( "make_fetch_repos.sh", [base_repo_path()], )?)?; diff --git a/vendor/gix/src/remote/connection/mod.rs b/vendor/gix/src/remote/connection/mod.rs index 02a09926a..f9b8aa7e6 100644 --- a/vendor/gix/src/remote/connection/mod.rs +++ b/vendor/gix/src/remote/connection/mod.rs @@ -17,6 +17,7 @@ pub struct Connection<'a, 'repo, T> { pub(crate) authenticate: Option<AuthenticateFn<'a>>, pub(crate) transport_options: Option<Box<dyn std::any::Any>>, pub(crate) transport: T, + pub(crate) trace: bool, } mod access; diff --git a/vendor/gix/src/remote/connection/ref_map.rs b/vendor/gix/src/remote/connection/ref_map.rs index f1b40d56e..bf53cf35f 100644 --- a/vendor/gix/src/remote/connection/ref_map.rs +++ b/vendor/gix/src/remote/connection/ref_map.rs @@ -95,7 +95,7 @@ where #[gix_protocol::maybe_async::maybe_async] 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) + gix_protocol::indicate_end_of_interaction(&mut self.transport, self.trace) .await .ok(); res @@ -112,6 +112,7 @@ where mut extra_refspecs, }: Options, ) -> Result<fetch::RefMap, Error> { + let _span = gix_trace::coarse!("remote::Connection::ref_map()"); let null = gix_hash::ObjectId::null(gix_hash::Kind::Sha1); // OK to hardcode Sha1, it's not supposed to match, ever. if let Some(tag_spec) = self.remote.fetch_tags.to_refspec().map(|spec| spec.to_owned()) { @@ -186,6 +187,7 @@ where refspecs: &[gix_refspec::RefSpec], mut progress: impl Progress, ) -> Result<HandshakeWithRefs, Error> { + let _span = gix_trace::coarse!("remote::Connection::fetch_refs()"); let mut credentials_storage; let url = self.transport.to_url(); let authenticate = match self.authenticate.as_mut() { @@ -241,6 +243,7 @@ where Ok(gix_protocol::ls_refs::Action::Continue) }, &mut progress, + self.trace, ) .await? } diff --git a/vendor/gix/src/repository/config/mod.rs b/vendor/gix/src/repository/config/mod.rs index 618ccf0f6..6966e1276 100644 --- a/vendor/gix/src/repository/config/mod.rs +++ b/vendor/gix/src/repository/config/mod.rs @@ -79,6 +79,52 @@ impl crate::Repository { Ok(opts) } + /// Return the context to be passed to any spawned program that is supposed to interact with the repository, like + /// hooks or filters. + #[cfg(feature = "attributes")] + pub fn command_context(&self) -> Result<gix_command::Context, config::command_context::Error> { + use crate::config::{ + cache::util::ApplyLeniency, + tree::{gitoxide, Key}, + }; + + let pathspec_boolean = |key: &'static config::tree::keys::Boolean| { + self.config + .resolved + .boolean("gitoxide", Some("pathspec".into()), key.name()) + .map(|value| key.enrich_error(value)) + .transpose() + .with_leniency(self.config.lenient_config) + }; + + Ok(gix_command::Context { + stderr: { + let key = &gitoxide::Core::EXTERNAL_COMMAND_STDERR; + self.config + .resolved + .boolean("gitoxide", Some("core".into()), key.name()) + .map(|value| key.enrich_error(value)) + .transpose() + .with_leniency(self.config.lenient_config)? + .unwrap_or(true) + .into() + }, + git_dir: self.git_dir().to_owned().into(), + worktree_dir: self.work_dir().map(ToOwned::to_owned), + no_replace_objects: config::shared::is_replace_refs_enabled( + &self.config.resolved, + self.config.lenient_config, + self.filter_config_section(), + )? + .map(|enabled| !enabled), + ref_namespace: self.refs.namespace.as_ref().map(|ns| ns.as_bstr().to_owned()), + literal_pathspecs: pathspec_boolean(&gitoxide::Pathspec::LITERAL)?, + glob_pathspecs: pathspec_boolean(&gitoxide::Pathspec::GLOB)? + .or(pathspec_boolean(&gitoxide::Pathspec::NOGLOB)?), + icase_pathspecs: pathspec_boolean(&gitoxide::Pathspec::ICASE)?, + }) + } + /// The kind of object hash the repository is configured to use. pub fn object_hash(&self) -> gix_hash::Kind { self.config.object_hash diff --git a/vendor/gix/src/repository/config/transport.rs b/vendor/gix/src/repository/config/transport.rs index 99b5a7f47..907e2a4fb 100644 --- a/vendor/gix/src/repository/config/transport.rs +++ b/vendor/gix/src/repository/config/transport.rs @@ -405,6 +405,30 @@ impl crate::Repository { } } + { + let key = "gitoxide.http.sslNoVerify"; + let ssl_no_verify = config + .boolean_filter_by_key(key, &mut trusted_only) + .map(|value| config::tree::gitoxide::Http::SSL_NO_VERIFY.enrich_error(value)) + .transpose() + .with_leniency(lenient) + .map_err(config::transport::http::Error::from)? + .unwrap_or_default(); + + if ssl_no_verify { + opts.ssl_verify = false; + } else { + let key = "http.sslVerify"; + opts.ssl_verify = config + .boolean_filter_by_key(key, &mut trusted_only) + .map(|value| config::tree::Http::SSL_VERIFY.enrich_error(value)) + .transpose() + .with_leniency(lenient) + .map_err(config::transport::http::Error::from)? + .unwrap_or(true); + } + } + #[cfg(feature = "blocking-http-transport-curl")] { let key = "http.schannelCheckRevoke"; diff --git a/vendor/gix/src/repository/diff.rs b/vendor/gix/src/repository/diff.rs new file mode 100644 index 000000000..cb1d070a2 --- /dev/null +++ b/vendor/gix/src/repository/diff.rs @@ -0,0 +1,45 @@ +use crate::Repository; + +/// +pub mod resource_cache { + /// The error returned by [Repository::diff_resource_cache()](super::Repository::diff_resource_cache()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Could not obtain resource cache for diffing")] + ResourceCache(#[from] crate::diff::resource_cache::Error), + #[error(transparent)] + Index(#[from] crate::repository::index_or_load_from_head::Error), + } +} + +/// Diff-utilities +impl Repository { + /// Create a resource cache for diffable objects, and configured with everything it needs to know to perform diffs + /// faithfully just like `git` would. + /// `mode` controls what version of a resource should be diffed. + /// `worktree_roots` determine if files can be read from the worktree, where each side of the diff operation can + /// be represented by its own worktree root. `.gitattributes` are automatically read from the worktree if at least + /// one worktree is present. + /// + /// Note that attributes will always be obtained from the current `HEAD` index even if the resources being diffed + /// might live in another tree. Further, if one of the `worktree_roots` are set, attributes will also be read from + /// the worktree. Otherwise, it will be skipped and attributes are read from the index tree instead. + pub fn diff_resource_cache( + &self, + mode: gix_diff::blob::pipeline::Mode, + worktree_roots: gix_diff::blob::pipeline::WorktreeRoots, + ) -> Result<gix_diff::blob::Platform, resource_cache::Error> { + Ok(crate::diff::resource_cache( + self, + &*self.index_or_load_from_head()?, + mode, + if worktree_roots.new_root.is_some() || worktree_roots.old_root.is_some() { + gix_worktree::stack::state::attributes::Source::WorktreeThenIdMapping + } else { + gix_worktree::stack::state::attributes::Source::IdMapping + }, + worktree_roots, + )?) + } +} diff --git a/vendor/gix/src/repository/filter.rs b/vendor/gix/src/repository/filter.rs index 3aacb1a3d..68644ca98 100644 --- a/vendor/gix/src/repository/filter.rs +++ b/vendor/gix/src/repository/filter.rs @@ -2,7 +2,7 @@ use crate::{filter, repository::IndexPersistedOrInMemory, Id, Repository}; /// pub mod pipeline { - /// The error returned by [Repository::filter_pipeline()][super::Repository::filter_pipeline()]. + /// The error returned by [Repository::filter_pipeline()](super::Repository::filter_pipeline()). #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -24,7 +24,7 @@ pub mod pipeline { impl Repository { /// Configure a pipeline for converting byte buffers to the worktree representation, and byte streams to the git-internal /// representation. Also return the index that was used when initializing the pipeline as it may be useful when calling - /// [convert_to_git()][filter::Pipeline::convert_to_git()]. + /// [convert_to_git()](filter::Pipeline::convert_to_git()). /// Bare repositories will either use `HEAD^{tree}` for accessing all relevant worktree files or the given `tree_if_bare`. /// /// Note that this is considered a primitive as it operates on data directly and will not have permanent effects. diff --git a/vendor/gix/src/repository/graph.rs b/vendor/gix/src/repository/graph.rs index f4f2b18cc..7d59589ed 100644 --- a/vendor/gix/src/repository/graph.rs +++ b/vendor/gix/src/repository/graph.rs @@ -1,5 +1,3 @@ -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. @@ -16,11 +14,7 @@ impl crate::Repository { /// of the commit walk. pub fn revision_graph<T>(&self) -> gix_revwalk::Graph<'_, T> { gix_revwalk::Graph::new( - |id, buf| { - self.objects - .try_find(id, buf) - .map(|r| r.and_then(gix_object::Data::try_into_commit_iter)) - }, + &self.objects, self.config .may_use_commit_graph() .unwrap_or(true) diff --git a/vendor/gix/src/repository/index.rs b/vendor/gix/src/repository/index.rs index 59666fc5f..85a1a664b 100644 --- a/vendor/gix/src/repository/index.rs +++ b/vendor/gix/src/repository/index.rs @@ -1,5 +1,3 @@ -use gix_odb::FindExt; - use crate::{config::cache::util::ApplyLeniencyDefault, repository::IndexPersistedOrInMemory, worktree}; /// Index access @@ -113,7 +111,7 @@ impl crate::Repository { tree: &gix_hash::oid, ) -> Result<gix_index::File, gix_traverse::tree::breadthfirst::Error> { Ok(gix_index::File::from_state( - gix_index::State::from_tree(tree, |oid, buf| self.objects.find_tree_iter(oid, buf).ok())?, + gix_index::State::from_tree(tree, &self.objects)?, self.git_dir().join("index"), )) } diff --git a/vendor/gix/src/repository/kind.rs b/vendor/gix/src/repository/kind.rs index 88779e0cc..a9af07e23 100644 --- a/vendor/gix/src/repository/kind.rs +++ b/vendor/gix/src/repository/kind.rs @@ -13,7 +13,7 @@ impl From<gix_discover::repository::Kind> for Kind { gix_discover::repository::Kind::Submodule { .. } | gix_discover::repository::Kind::SubmoduleGitDir => { Kind::WorkTree { is_linked: false } } - gix_discover::repository::Kind::Bare => Kind::Bare, + gix_discover::repository::Kind::PossiblyBare => Kind::Bare, gix_discover::repository::Kind::WorkTreeGitDir { .. } => Kind::WorkTree { is_linked: true }, gix_discover::repository::Kind::WorkTree { linked_git_dir } => Kind::WorkTree { is_linked: linked_git_dir.is_some(), diff --git a/vendor/gix/src/repository/mod.rs b/vendor/gix/src/repository/mod.rs index e3742894b..28aa5aa8e 100644 --- a/vendor/gix/src/repository/mod.rs +++ b/vendor/gix/src/repository/mod.rs @@ -40,6 +40,9 @@ pub mod attributes; mod cache; mod config; /// +#[cfg(feature = "blob-diff")] +pub mod diff; +/// #[cfg(feature = "attributes")] pub mod filter; mod graph; @@ -124,6 +127,8 @@ pub mod worktree_stream { AttributesCache(#[from] crate::config::attribute_stack::Error), #[error(transparent)] FilterPipeline(#[from] crate::filter::pipeline::options::Error), + #[error(transparent)] + CommandContext(#[from] crate::config::command_context::Error), #[error("Needed {id} to be a tree to turn into a workspace stream, got {actual}")] NotATree { id: gix_hash::ObjectId, diff --git a/vendor/gix/src/repository/object.rs b/vendor/gix/src/repository/object.rs index 0b894939f..77f188bad 100644 --- a/vendor/gix/src/repository/object.rs +++ b/vendor/gix/src/repository/object.rs @@ -3,7 +3,8 @@ use std::{convert::TryInto, ops::DerefMut}; use gix_hash::ObjectId; use gix_macros::momo; -use gix_odb::{Find, FindExt, Header, HeaderExt, Write}; +use gix_object::{Exists, Find, FindExt}; +use gix_odb::{Header, HeaderExt, Write}; use gix_ref::{ transaction::{LogChange, PreviousValue, RefLog}, FullName, @@ -70,7 +71,7 @@ impl crate::Repository { if id == ObjectId::empty_tree(self.object_hash()) { true } else { - self.objects.contains(id) + self.objects.exists(id) } } @@ -140,7 +141,7 @@ impl crate::Repository { fn write_object_inner(&self, buf: &[u8], kind: gix_object::Kind) -> Result<Id<'_>, object::write::Error> { let oid = gix_object::compute_hash(self.object_hash(), kind, buf); - if self.objects.contains(&oid) { + if self.objects.exists(&oid) { return Ok(oid.attach(self)); } @@ -158,7 +159,7 @@ impl crate::Repository { pub fn write_blob(&self, bytes: impl AsRef<[u8]>) -> Result<Id<'_>, object::write::Error> { let bytes = bytes.as_ref(); let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, bytes); - if self.objects.contains(&oid) { + if self.objects.exists(&oid) { return Ok(oid.attach(self)); } self.objects @@ -185,7 +186,7 @@ impl crate::Repository { fn write_blob_stream_inner(&self, buf: &[u8]) -> Result<Id<'_>, object::write::Error> { let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, buf); - if self.objects.contains(&oid) { + if self.objects.exists(&oid) { return Ok(oid.attach(self)); } diff --git a/vendor/gix/src/repository/reference.rs b/vendor/gix/src/repository/reference.rs index e57ca63c0..b977c6ea8 100644 --- a/vendor/gix/src/repository/reference.rs +++ b/vendor/gix/src/repository/reference.rs @@ -174,7 +174,8 @@ impl crate::Repository { .attach(self)) } - /// Resolve the `HEAD` reference, follow and peel its target and obtain its object id. + /// Resolve the `HEAD` reference, follow and peel its target and obtain its object id, + /// following symbolic references and tags until a commit is found. /// /// Note that this may fail for various reasons, most notably because the repository /// is freshly initialized and doesn't have any commits yet. @@ -182,12 +183,7 @@ impl crate::Repository { /// Also note that the returned id is likely to point to a commit, but could also /// point to a tree or blob. It won't, however, point to a tag as these are always peeled. pub fn head_id(&self) -> Result<crate::Id<'_>, reference::head_id::Error> { - let mut head = self.head()?; - head.peel_to_id_in_place() - .ok_or_else(|| reference::head_id::Error::Unborn { - name: head.referent_name().expect("unborn").to_owned(), - })? - .map_err(Into::into) + Ok(self.head()?.into_peeled_id()?) } /// Return the name to the symbolic reference `HEAD` points to, or `None` if the head is detached. @@ -203,7 +199,8 @@ impl crate::Repository { Ok(self.head()?.try_into_referent()) } - /// Return the commit object the `HEAD` reference currently points to after peeling it fully. + /// Return the commit object the `HEAD` reference currently points to after peeling it fully, + /// following symbolic references and tags until a commit is found. /// /// Note that this may fail for various reasons, most notably because the repository /// is freshly initialized and doesn't have any commits yet. It could also fail if the @@ -212,13 +209,14 @@ impl crate::Repository { Ok(self.head()?.peel_to_commit_in_place()?) } - /// Return the tree id the `HEAD` reference currently points to after peeling it fully. + /// Return the tree id the `HEAD` reference currently points to after peeling it fully, + /// following symbolic references and tags until a commit is found. /// /// Note that this may fail for various reasons, most notably because the repository /// is freshly initialized and doesn't have any commits yet. It could also fail if the /// head does not point to a commit. pub fn head_tree_id(&self) -> Result<crate::Id<'_>, reference::head_tree_id::Error> { - Ok(self.head()?.peel_to_commit_in_place()?.tree_id()?) + Ok(self.head_commit()?.tree_id()?) } /// Find the reference with the given partial or full `name`, like `main`, `HEAD`, `heads/branch` or `origin/other`, diff --git a/vendor/gix/src/repository/revision.rs b/vendor/gix/src/repository/revision.rs index bb9b56d57..b59a3a94d 100644 --- a/vendor/gix/src/repository/revision.rs +++ b/vendor/gix/src/repository/revision.rs @@ -1,6 +1,7 @@ -use crate::{bstr::BStr, revision, Id}; use gix_macros::momo; +use crate::{bstr::BStr, revision, Id}; + /// Methods for resolving revisions by spec or working with the commit graph. impl crate::Repository { /// Parse a revision specification and turn it into the object(s) it describes, similar to `git rev-parse`. diff --git a/vendor/gix/src/repository/worktree.rs b/vendor/gix/src/repository/worktree.rs index cc6f0bf73..529243896 100644 --- a/vendor/gix/src/repository/worktree.rs +++ b/vendor/gix/src/repository/worktree.rs @@ -62,7 +62,7 @@ impl crate::Repository { &self, id: impl Into<gix_hash::ObjectId>, ) -> Result<(gix_worktree_stream::Stream, gix_index::File), crate::repository::worktree_stream::Error> { - use gix_odb::{FindExt, HeaderExt}; + use gix_odb::HeaderExt; let id = id.into(); let header = self.objects.header(id)?; if !header.kind().is_tree() { @@ -79,18 +79,14 @@ impl crate::Repository { let mut cache = self .attributes_only(&index, gix_worktree::stack::state::attributes::Source::IdMapping)? .detach(); - let pipeline = - gix_filter::Pipeline::new(cache.attributes_collection(), crate::filter::Pipeline::options(self)?); + let pipeline = gix_filter::Pipeline::new(self.command_context()?, crate::filter::Pipeline::options(self)?); let objects = self.objects.clone().into_arc().expect("TBD error handling"); let stream = gix_worktree_stream::from_tree( id, - { - let objects = objects.clone(); - move |id, buf| objects.find(id, buf) - }, + objects.clone(), pipeline, move |path, mode, attrs| -> std::io::Result<()> { - let entry = cache.at_entry(path, Some(mode.is_tree()), |id, buf| objects.find_blob(id, buf))?; + let entry = cache.at_entry(path, Some(mode.is_tree()), &objects)?; entry.matching_attributes(attrs); Ok(()) }, diff --git a/vendor/gix/src/revision/mod.rs b/vendor/gix/src/revision/mod.rs index 3de528ecd..62fe72dd3 100644 --- a/vendor/gix/src/revision/mod.rs +++ b/vendor/gix/src/revision/mod.rs @@ -7,6 +7,7 @@ pub use gix_revision as plumbing; /// pub mod walk; +use crate::bstr::BString; pub use walk::iter::Walk; /// @@ -22,6 +23,8 @@ pub mod spec; #[cfg(feature = "revision")] pub struct Spec<'repo> { pub(crate) inner: gix_revision::Spec, + /// The path we encountered in the revspec, like `@:<path>` or `@..@~1:<path>`. + pub(crate) path: Option<(BString, gix_object::tree::EntryMode)>, /// The first name of a reference as seen while parsing a `RevSpec`, for completeness. pub(crate) first_ref: Option<gix_ref::Reference>, /// The second name of a reference as seen while parsing a `RevSpec`, for completeness. diff --git a/vendor/gix/src/revision/spec/mod.rs b/vendor/gix/src/revision/spec/mod.rs index a6a6eb739..af58ecdff 100644 --- a/vendor/gix/src/revision/spec/mod.rs +++ b/vendor/gix/src/revision/spec/mod.rs @@ -1,3 +1,4 @@ +use crate::bstr::BStr; use crate::{ext::ReferenceExt, revision::Spec, Id, Reference}; /// @@ -37,6 +38,7 @@ impl<'repo> Spec<'repo> { pub fn from_id(id: Id<'repo>) -> Self { Spec { inner: gix_revision::Spec::Include(id.inner), + path: None, repo: id.repo, first_ref: None, second_ref: None, @@ -62,6 +64,13 @@ impl<'repo> Spec<'repo> { ) } + /// Return the path encountered in specs like `@:<path>` or `:<path>`, along with the kind of object it represents. + /// + /// Note that there can only be one as paths always terminates further revspec parsing. + pub fn path_and_mode(&self) -> Option<(&BStr, gix_object::tree::EntryMode)> { + self.path.as_ref().map(|(p, mode)| (p.as_ref(), *mode)) + } + /// Return the name of the first reference we encountered while resolving the rev-spec, or `None` if a short hash /// was used. For example, `@` might yield `Some(HEAD)`, but `abcd` yields `None`. pub fn first_reference(&self) -> Option<&gix_ref::Reference> { diff --git a/vendor/gix/src/revision/spec/parse/delegate/mod.rs b/vendor/gix/src/revision/spec/parse/delegate/mod.rs index eaf7f5fd6..374906eaf 100644 --- a/vendor/gix/src/revision/spec/parse/delegate/mod.rs +++ b/vendor/gix/src/revision/spec/parse/delegate/mod.rs @@ -17,6 +17,7 @@ impl<'repo> Delegate<'repo> { Delegate { refs: Default::default(), objs: Default::default(), + paths: Default::default(), ambiguous_objects: Default::default(), idx: 0, kind: None, @@ -100,6 +101,7 @@ impl<'repo> Delegate<'repo> { let range = zero_or_one_objects_or_ambiguity_err(self.objs, self.prefix, self.err, self.repo)?; Ok(crate::revision::Spec { + path: self.paths[0].take().or(self.paths[1].take()), first_ref: self.refs[0].take(), second_ref: self.refs[1].take(), inner: kind_to_spec(self.kind, range)?, diff --git a/vendor/gix/src/revision/spec/parse/delegate/navigate.rs b/vendor/gix/src/revision/spec/parse/delegate/navigate.rs index 51feb1d76..731a24136 100644 --- a/vendor/gix/src/revision/spec/parse/delegate/navigate.rs +++ b/vendor/gix/src/revision/spec/parse/delegate/navigate.rs @@ -121,7 +121,7 @@ impl<'repo> delegate::Navigate for Delegate<'repo> { let lookup_path = |obj: &ObjectId| { let tree_id = peel(repo, obj, gix_object::Kind::Tree)?; if path.is_empty() { - return Ok(tree_id); + return Ok((tree_id, gix_object::tree::EntryKind::Tree.into())); } let mut tree = repo.find_object(tree_id)?.into_tree(); let entry = @@ -131,11 +131,17 @@ impl<'repo> delegate::Navigate for Delegate<'repo> { object: obj.attach(repo).shorten_or_id(), tree: tree_id.attach(repo).shorten_or_id(), })?; - Ok(entry.object_id()) + Ok((entry.object_id(), entry.mode())) }; for obj in objs.iter() { match lookup_path(obj) { - Ok(replace) => replacements.push((*obj, replace)), + Ok((replace, mode)) => { + if !path.is_empty() { + // Technically this is letting the last one win, but so be it. + self.paths[self.idx] = Some((path.to_owned(), mode)); + } + replacements.push((*obj, replace)) + } Err(err) => errors.push((*obj, err)), } } @@ -306,6 +312,14 @@ impl<'repo> delegate::Navigate for Delegate<'repo> { self.objs[self.idx] .get_or_insert_with(HashSet::default) .insert(entry.id); + + self.paths[self.idx] = Some(( + path.to_owned(), + entry + .mode + .to_tree_entry_mode() + .unwrap_or(gix_object::tree::EntryKind::Blob.into()), + )); Some(()) } None => { diff --git a/vendor/gix/src/revision/spec/parse/mod.rs b/vendor/gix/src/revision/spec/parse/mod.rs index 950dfa004..e45847763 100644 --- a/vendor/gix/src/revision/spec/parse/mod.rs +++ b/vendor/gix/src/revision/spec/parse/mod.rs @@ -7,6 +7,7 @@ use gix_revision::spec::parse; use crate::{bstr::BStr, revision::Spec, Repository}; mod types; +use crate::bstr::BString; pub use types::{Error, ObjectKindHint, Options, RefsHint}; /// @@ -45,6 +46,9 @@ impl<'repo> Spec<'repo> { struct Delegate<'repo> { refs: [Option<gix_ref::Reference>; 2], objs: [Option<HashSet<ObjectId>>; 2], + /// Path specified like `@:<path>` or `:<path>` for later use when looking up specs. + /// Note that it terminates spec parsing, so it's either `0` or `1`, never both. + paths: [Option<(BString, gix_object::tree::EntryMode)>; 2], /// The originally encountered ambiguous objects for potential later use in errors. ambiguous_objects: [Option<HashSet<ObjectId>>; 2], idx: usize, diff --git a/vendor/gix/src/revision/walk.rs b/vendor/gix/src/revision/walk.rs index 5e76ad898..19d15d569 100644 --- a/vendor/gix/src/revision/walk.rs +++ b/vendor/gix/src/revision/walk.rs @@ -1,5 +1,5 @@ use gix_hash::ObjectId; -use gix_odb::FindExt; +use gix_object::FindExt; use crate::{ext::ObjectIdExt, revision, Repository}; @@ -169,7 +169,7 @@ impl<'repo> Platform<'repo> { gix_traverse::commit::Ancestors::filtered( tips, gix_traverse::commit::ancestors::State::default(), - move |oid, buf| repo.objects.find_commit_iter(oid, buf), + &repo.objects, { // Note that specific shallow handling for commit-graphs isn't needed as these contain // all information there is, and exclude shallow parents to be structurally consistent. diff --git a/vendor/gix/src/submodule/mod.rs b/vendor/gix/src/submodule/mod.rs index 52c5938fc..fcfffd26f 100644 --- a/vendor/gix/src/submodule/mod.rs +++ b/vendor/gix/src/submodule/mod.rs @@ -7,7 +7,6 @@ use std::{ path::PathBuf, }; -use gix_odb::FindExt; pub use gix_submodule::*; use crate::{bstr::BStr, repository::IndexPersistedOrInMemory, Repository, Submodule}; @@ -147,9 +146,7 @@ impl<'repo> Submodule<'repo> { &mut |relative_path, case, is_dir, out| { attributes .set_case(case) - .at_entry(relative_path, Some(is_dir), |id, buf| { - self.state.repo.objects.find_blob(id, buf) - }) + .at_entry(relative_path, Some(is_dir), &self.state.repo.objects) .map_or(false, |platform| platform.matching_attributes(out)) } })?; @@ -184,7 +181,7 @@ impl<'repo> Submodule<'repo> { .head_commit()? .tree()? .peel_to_entry_by_path(gix_path::from_bstr(path.as_ref()))? - .and_then(|entry| (entry.mode() == gix_object::tree::EntryMode::Commit).then_some(entry.inner.oid))) + .and_then(|entry| (entry.mode().is_commit()).then_some(entry.inner.oid))) } /// Return the path at which the repository of the submodule should be located. |