From 9835e2ae736235810b4ea1c162ca5e65c547e770 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 18 May 2024 04:49:50 +0200 Subject: Merging upstream version 1.71.1+dfsg1. Signed-off-by: Daniel Baumann --- vendor/gix/src/attributes.rs | 9 + vendor/gix/src/clone/access.rs | 61 ++++++ vendor/gix/src/clone/checkout.rs | 9 +- vendor/gix/src/clone/fetch/mod.rs | 85 ++------ vendor/gix/src/clone/mod.rs | 53 ++++- vendor/gix/src/commit.rs | 9 +- vendor/gix/src/config/cache/access.rs | 134 +++++++++---- vendor/gix/src/config/cache/incubate.rs | 34 +++- vendor/gix/src/config/cache/init.rs | 71 ++++--- vendor/gix/src/config/cache/util.rs | 3 +- vendor/gix/src/config/mod.rs | 45 ++++- vendor/gix/src/config/tree/mod.rs | 9 +- vendor/gix/src/config/tree/sections/clone.rs | 4 +- vendor/gix/src/config/tree/sections/gitoxide.rs | 48 ++++- vendor/gix/src/config/tree/sections/index.rs | 62 ++++++ vendor/gix/src/config/tree/sections/mod.rs | 5 + vendor/gix/src/create.rs | 4 +- vendor/gix/src/diff.rs | 17 ++ vendor/gix/src/id.rs | 12 +- vendor/gix/src/lib.rs | 95 +++------ vendor/gix/src/object/commit.rs | 4 +- vendor/gix/src/object/mod.rs | 10 +- vendor/gix/src/object/tag.rs | 11 ++ vendor/gix/src/object/tree/diff/mod.rs | 9 +- vendor/gix/src/object/tree/mod.rs | 17 ++ vendor/gix/src/open/mod.rs | 19 +- vendor/gix/src/open/options.rs | 5 +- vendor/gix/src/open/permissions.rs | 215 +++++++++++++++++++++ vendor/gix/src/open/repository.rs | 24 ++- vendor/gix/src/prelude.rs | 4 + vendor/gix/src/progress.rs | 3 + vendor/gix/src/remote/connect.rs | 16 +- vendor/gix/src/remote/connection/access.rs | 22 ++- vendor/gix/src/remote/connection/fetch/error.rs | 15 ++ vendor/gix/src/remote/connection/fetch/mod.rs | 28 ++- .../gix/src/remote/connection/fetch/negotiate.rs | 15 +- .../src/remote/connection/fetch/receive_pack.rs | 130 +++++++++++-- vendor/gix/src/remote/connection/mod.rs | 3 +- vendor/gix/src/remote/connection/ref_map.rs | 21 +- vendor/gix/src/remote/fetch.rs | 50 ++++- vendor/gix/src/remote/save.rs | 2 + vendor/gix/src/repository/attributes.rs | 50 +++++ vendor/gix/src/repository/excludes.rs | 45 +++++ vendor/gix/src/repository/impls.rs | 4 + vendor/gix/src/repository/init.rs | 3 + vendor/gix/src/repository/mod.rs | 4 +- vendor/gix/src/repository/object.rs | 54 +++++- vendor/gix/src/repository/permissions.rs | 119 ++++++++---- vendor/gix/src/repository/shallow.rs | 65 +++++++ vendor/gix/src/repository/worktree.rs | 24 +-- vendor/gix/src/revision/spec/parse/types.rs | 11 +- vendor/gix/src/revision/walk.rs | 102 +++++----- vendor/gix/src/shallow.rs | 92 +++++++++ vendor/gix/src/state.rs | 25 +++ vendor/gix/src/types.rs | 2 + vendor/gix/src/worktree/mod.rs | 105 +++++----- 56 files changed, 1600 insertions(+), 497 deletions(-) create mode 100644 vendor/gix/src/attributes.rs create mode 100644 vendor/gix/src/clone/access.rs create mode 100644 vendor/gix/src/config/tree/sections/index.rs create mode 100644 vendor/gix/src/diff.rs create mode 100644 vendor/gix/src/open/permissions.rs create mode 100644 vendor/gix/src/prelude.rs create mode 100644 vendor/gix/src/progress.rs create mode 100644 vendor/gix/src/repository/attributes.rs create mode 100644 vendor/gix/src/repository/excludes.rs create mode 100644 vendor/gix/src/repository/shallow.rs create mode 100644 vendor/gix/src/shallow.rs create mode 100644 vendor/gix/src/state.rs (limited to 'vendor/gix/src') diff --git a/vendor/gix/src/attributes.rs b/vendor/gix/src/attributes.rs new file mode 100644 index 000000000..bb8636460 --- /dev/null +++ b/vendor/gix/src/attributes.rs @@ -0,0 +1,9 @@ +/// The error returned by [`Repository::attributes()`][crate::Repository::attributes()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + ConfigureAttributes(#[from] crate::config::attribute_stack::Error), + #[error(transparent)] + ConfigureExcludes(#[from] crate::config::exclude_stack::Error), +} diff --git a/vendor/gix/src/clone/access.rs b/vendor/gix/src/clone/access.rs new file mode 100644 index 000000000..5b6e5fbab --- /dev/null +++ b/vendor/gix/src/clone/access.rs @@ -0,0 +1,61 @@ +use crate::bstr::BString; +use crate::clone::PrepareFetch; +use crate::Repository; + +/// Builder +impl PrepareFetch { + /// Use `f` to apply arbitrary changes to the remote that is about to be used to fetch a pack. + /// + /// The passed in `remote` will be un-named and pre-configured to be a default remote as we know it from git-clone. + /// It is not yet present in the configuration of the repository, + /// but each change it will eventually be written to the configuration prior to performing a the fetch operation, + /// _all changes done in `f()` will be persisted_. + /// + /// It can also be used to configure additional options, like those for fetching tags. Note that + /// [with_fetch_tags()][crate::Remote::with_fetch_tags()] should be called here to configure the clone as desired. + /// Otherwise a clone is configured to be complete and fetches all tags, not only those reachable from all branches. + pub fn configure_remote( + mut self, + f: impl FnMut(crate::Remote<'_>) -> Result, Box> + 'static, + ) -> Self { + self.configure_remote = Some(Box::new(f)); + self + } + + /// Set the remote's name to the given value after it was configured using the function provided via + /// [`configure_remote()`][Self::configure_remote()]. + /// + /// If not set here, it defaults to `origin` or the value of `clone.defaultRemoteName`. + pub fn with_remote_name(mut self, name: impl Into) -> Result { + self.remote_name = Some(crate::remote::name::validated(name)?); + Ok(self) + } + + /// Make this clone a shallow one with the respective choice of shallow-ness. + pub fn with_shallow(mut self, shallow: crate::remote::fetch::Shallow) -> Self { + self.shallow = shallow; + self + } +} + +/// Consumption +impl PrepareFetch { + /// Persist the contained repository as is even if an error may have occurred when fetching from the remote. + pub fn persist(mut self) -> Repository { + self.repo.take().expect("present and consumed once") + } +} + +impl Drop for PrepareFetch { + fn drop(&mut self) { + if let Some(repo) = self.repo.take() { + std::fs::remove_dir_all(repo.work_dir().unwrap_or_else(|| repo.path())).ok(); + } + } +} + +impl From for Repository { + fn from(prep: PrepareFetch) -> Self { + prep.persist() + } +} diff --git a/vendor/gix/src/clone/checkout.rs b/vendor/gix/src/clone/checkout.rs index 50d235f13..823005551 100644 --- a/vendor/gix/src/clone/checkout.rs +++ b/vendor/gix/src/clone/checkout.rs @@ -27,8 +27,7 @@ pub mod main_worktree { CheckoutOptions(#[from] crate::config::checkout_options::Error), #[error(transparent)] IndexCheckout( - #[from] - gix_worktree::index::checkout::Error>, + #[from] gix_worktree::checkout::Error>, ), #[error("Failed to reopen object database as Arc (only if thread-safety wasn't compiled in)")] OpenArcOdb(#[from] std::io::Error), @@ -69,7 +68,7 @@ pub mod main_worktree { &mut self, mut progress: impl crate::Progress, should_interrupt: &AtomicBool, - ) -> Result<(Repository, gix_worktree::index::checkout::Outcome), Error> { + ) -> Result<(Repository, gix_worktree::checkout::Outcome), Error> { let repo = self .repo .as_ref() @@ -82,7 +81,7 @@ pub mod main_worktree { None => { return Ok(( self.repo.take().expect("still present"), - gix_worktree::index::checkout::Outcome::default(), + gix_worktree::checkout::Outcome::default(), )) } }; @@ -103,7 +102,7 @@ pub mod main_worktree { bytes.init(None, crate::progress::bytes()); let start = std::time::Instant::now(); - let outcome = gix_worktree::index::checkout( + let outcome = gix_worktree::checkout( &mut index, workdir, { diff --git a/vendor/gix/src/clone/fetch/mod.rs b/vendor/gix/src/clone/fetch/mod.rs index d663b47ea..59f820675 100644 --- a/vendor/gix/src/clone/fetch/mod.rs +++ b/vendor/gix/src/clone/fetch/mod.rs @@ -1,9 +1,8 @@ -use crate::{bstr::BString, clone::PrepareFetch, Repository}; +use crate::clone::PrepareFetch; /// The error returned by [`PrepareFetch::fetch_only()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] -#[cfg(feature = "blocking-network-client")] pub enum Error { #[error(transparent)] Connect(#[from] crate::remote::connect::Error), @@ -15,6 +14,8 @@ pub enum Error { RemoteInit(#[from] crate::remote::init::Error), #[error("Custom configuration of remote to clone from failed")] RemoteConfiguration(#[source] Box), + #[error("Custom configuration of connection to use when cloning failed")] + RemoteConnection(#[source] Box), #[error(transparent)] RemoteName(#[from] crate::config::remote::symbolic_name::Error), #[error("Failed to load repo-local git configuration before writing")] @@ -26,7 +27,7 @@ pub enum Error { #[error("The remote HEAD points to a reference named {head_ref_name:?} which is invalid.")] InvalidHeadRef { source: gix_validate::refname::Error, - head_ref_name: BString, + head_ref_name: crate::bstr::BString, }, #[error("Failed to update HEAD with values from remote")] HeadUpdate(#[from] crate::reference::edit::Error), @@ -43,12 +44,11 @@ impl PrepareFetch { /// it was newly initialized. /// /// Note that all data we created will be removed once this instance drops if the operation wasn't successful. - #[cfg(feature = "blocking-network-client")] pub fn fetch_only

( &mut self, - progress: P, + mut progress: P, should_interrupt: &std::sync::atomic::AtomicBool, - ) -> Result<(Repository, crate::remote::fetch::Outcome), Error> + ) -> Result<(crate::Repository, crate::remote::fetch::Outcome), Error> where P: crate::Progress, P::SubProgress: 'static, @@ -100,14 +100,19 @@ impl PrepareFetch { ) .expect("valid") .to_owned(); - let pending_pack: remote::fetch::Prepare<'_, '_, _, _> = - remote.connect(remote::Direction::Fetch, progress)?.prepare_fetch({ + let pending_pack: remote::fetch::Prepare<'_, '_, _> = { + let mut connection = remote.connect(remote::Direction::Fetch)?; + if let Some(f) = self.configure_connection.as_mut() { + f(&mut connection).map_err(|err| Error::RemoteConnection(err))?; + } + connection.prepare_fetch(&mut progress, { let mut opts = self.fetch_options.clone(); if !opts.extra_refspecs.contains(&head_refspec) { opts.extra_refspecs.push(head_refspec) } opts - })?; + })? + }; if pending_pack.ref_map().object_hash != repo.object_hash() { unimplemented!("configure repository to expect a different object hash as advertised by the server") } @@ -121,7 +126,8 @@ impl PrepareFetch { .with_reflog_message(RefLogMessage::Override { message: reflog_message.clone(), }) - .receive(should_interrupt)?; + .with_shallow(self.shallow.clone()) + .receive(progress, should_interrupt)?; util::append_config_to_repo_config(repo, config); util::update_head( @@ -135,7 +141,6 @@ impl PrepareFetch { } /// Similar to [`fetch_only()`][Self::fetch_only()`], but passes ownership to a utility type to configure a checkout operation. - #[cfg(feature = "blocking-network-client")] pub fn fetch_then_checkout

( &mut self, progress: P, @@ -150,63 +155,5 @@ impl PrepareFetch { } } -/// Builder -impl PrepareFetch { - /// Set additional options to adjust parts of the fetch operation that are not affected by the git configuration. - #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] - pub fn with_fetch_options(mut self, opts: crate::remote::ref_map::Options) -> Self { - self.fetch_options = opts; - self - } - /// Use `f` to apply arbitrary changes to the remote that is about to be used to fetch a pack. - /// - /// The passed in `remote` will be un-named and pre-configured to be a default remote as we know it from git-clone. - /// It is not yet present in the configuration of the repository, - /// but each change it will eventually be written to the configuration prior to performing a the fetch operation, - /// _all changes done in `f()` will be persisted_. - /// - /// It can also be used to configure additional options, like those for fetching tags. Note that - /// [with_fetch_tags()][crate::Remote::with_fetch_tags()] should be called here to configure the clone as desired. - /// Otherwise a clone is configured to be complete and fetches all tags, not only those reachable from all branches. - pub fn configure_remote( - mut self, - f: impl FnMut(crate::Remote<'_>) -> Result, Box> + 'static, - ) -> Self { - self.configure_remote = Some(Box::new(f)); - self - } - - /// Set the remote's name to the given value after it was configured using the function provided via - /// [`configure_remote()`][Self::configure_remote()]. - /// - /// If not set here, it defaults to `origin` or the value of `clone.defaultRemoteName`. - pub fn with_remote_name(mut self, name: impl Into) -> Result { - self.remote_name = Some(crate::remote::name::validated(name)?); - Ok(self) - } -} - -/// Consumption -impl PrepareFetch { - /// Persist the contained repository as is even if an error may have occurred when fetching from the remote. - pub fn persist(mut self) -> Repository { - self.repo.take().expect("present and consumed once") - } -} - -impl Drop for PrepareFetch { - fn drop(&mut self) { - if let Some(repo) = self.repo.take() { - std::fs::remove_dir_all(repo.work_dir().unwrap_or_else(|| repo.path())).ok(); - } - } -} - -impl From for Repository { - fn from(prep: PrepareFetch) -> Self { - prep.persist() - } -} - #[cfg(feature = "blocking-network-client")] mod util; diff --git a/vendor/gix/src/clone/mod.rs b/vendor/gix/src/clone/mod.rs index 249a66a42..43024e0b4 100644 --- a/vendor/gix/src/clone/mod.rs +++ b/vendor/gix/src/clone/mod.rs @@ -1,10 +1,16 @@ #![allow(clippy::result_large_err)] use std::convert::TryInto; -use crate::{bstr::BString, config::tree::gitoxide}; +use crate::{bstr::BString, config::tree::gitoxide, remote}; type ConfigureRemoteFn = Box) -> Result, Box>>; +#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] +type ConfigureConnectionFn = Box< + dyn FnMut( + &mut remote::Connection<'_, '_, Box>, + ) -> Result<(), Box>, +>; /// A utility to collect configuration on how to fetch from a remote and initiate a fetch operation. It will delete the newly /// created repository on when dropped without successfully finishing a fetch. @@ -16,12 +22,18 @@ pub struct PrepareFetch { remote_name: Option, /// A function to configure a remote prior to fetching a pack. configure_remote: Option, + /// A function to configure a connection before using it. + #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] + configure_connection: Option, /// Options for preparing a fetch operation. #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] - fetch_options: crate::remote::ref_map::Options, + fetch_options: remote::ref_map::Options, /// The url to clone from #[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))] url: gix_url::Url, + /// How to handle shallow clones + #[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))] + shallow: remote::fetch::Shallow, } /// The error returned by [`PrepareFetch::new()`]. @@ -99,6 +111,9 @@ impl PrepareFetch { repo: Some(repo), remote_name: None, configure_remote: None, + #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] + configure_connection: None, + shallow: remote::fetch::Shallow::NoChange, }) } } @@ -111,7 +126,41 @@ pub struct PrepareCheckout { pub(self) repo: Option, } +mod access; + +// This module encapsulates functionality that works with both feature toggles. Can be combined with `fetch` +// once async and clone are a thing. +#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] +mod access_feat { + use crate::clone::PrepareFetch; + + /// Builder + impl PrepareFetch { + /// Set a callback to use for configuring the connection to use right before connecting to the remote. + /// + /// It is most commonly used for custom configuration. + // TODO: tests + pub fn configure_connection( + mut self, + f: impl FnMut( + &mut crate::remote::Connection<'_, '_, Box>, + ) -> Result<(), Box> + + 'static, + ) -> Self { + self.configure_connection = Some(Box::new(f)); + self + } + + /// Set additional options to adjust parts of the fetch operation that are not affected by the git configuration. + pub fn with_fetch_options(mut self, opts: crate::remote::ref_map::Options) -> Self { + self.fetch_options = opts; + self + } + } +} + /// +#[cfg(feature = "blocking-network-client")] pub mod fetch; /// diff --git a/vendor/gix/src/commit.rs b/vendor/gix/src/commit.rs index 10fa6f675..a58954a36 100644 --- a/vendor/gix/src/commit.rs +++ b/vendor/gix/src/commit.rs @@ -62,9 +62,10 @@ pub mod describe { } /// A selector to choose what kind of references should contribute to names. - #[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] + #[derive(Default, Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] pub enum SelectRef { /// Only use annotated tags for names. + #[default] AnnotatedTags, /// Use all tags for names, annotated or plain reference. AllTags, @@ -146,12 +147,6 @@ pub mod describe { } } - impl Default for SelectRef { - fn default() -> Self { - SelectRef::AnnotatedTags - } - } - /// A support type to allow configuring a `git describe` operation pub struct Platform<'repo> { pub(crate) id: gix_hash::ObjectId, diff --git a/vendor/gix/src/config/cache/access.rs b/vendor/gix/src/config/cache/access.rs index 8244eaf27..77324efe3 100644 --- a/vendor/gix/src/config/cache/access.rs +++ b/vendor/gix/src/config/cache/access.rs @@ -1,6 +1,7 @@ #![allow(clippy::result_large_err)] use std::{borrow::Cow, path::PathBuf, time::Duration}; +use gix_attributes::Source; use gix_lock::acquire::Fail; use crate::{ @@ -9,7 +10,7 @@ use crate::{ config::{ cache::util::{ApplyLeniency, ApplyLeniencyDefault}, checkout_options, - tree::{Checkout, Core, Key}, + tree::{gitoxide, Checkout, Core, Key}, Cache, }, remote, @@ -137,7 +138,7 @@ impl Cache { pub(crate) fn checkout_options( &self, git_dir: &std::path::Path, - ) -> Result { + ) -> Result { fn boolean( me: &Cache, full_key: &str, @@ -154,63 +155,118 @@ impl Cache { .unwrap_or(default)) } - fn assemble_attribute_globals( - me: &Cache, - _git_dir: &std::path::Path, - ) -> Result { - let _attributes_file = match me - .trusted_file_path("core", None, Core::ATTRIBUTES_FILE.name) - .transpose()? - { - Some(attributes) => Some(attributes.into_owned()), - None => me.xdg_config_path("attributes").ok().flatten(), - }; - // TODO: implement gix_attributes::MatchGroup::::from_git_dir(), similar to what's done for `Ignore`. - Ok(Default::default()) - } - let thread_limit = self.apply_leniency( self.resolved .integer_filter_by_key("checkout.workers", &mut self.filter_config_section.clone()) .map(|value| Checkout::WORKERS.try_from_workers(value)), )?; - Ok(gix_worktree::index::checkout::Options { - fs: gix_worktree::fs::Capabilities { - precompose_unicode: boolean(self, "core.precomposeUnicode", &Core::PRECOMPOSE_UNICODE, false)?, - ignore_case: boolean(self, "core.ignoreCase", &Core::IGNORE_CASE, false)?, - executable_bit: boolean(self, "core.fileMode", &Core::FILE_MODE, true)?, - symlink: boolean(self, "core.symlinks", &Core::SYMLINKS, true)?, - }, + let capabilities = gix_fs::Capabilities { + precompose_unicode: boolean(self, "core.precomposeUnicode", &Core::PRECOMPOSE_UNICODE, false)?, + ignore_case: boolean(self, "core.ignoreCase", &Core::IGNORE_CASE, false)?, + executable_bit: boolean(self, "core.fileMode", &Core::FILE_MODE, true)?, + symlink: boolean(self, "core.symlinks", &Core::SYMLINKS, true)?, + }; + Ok(gix_worktree::checkout::Options { + attributes: self + .assemble_attribute_globals( + git_dir, + gix_worktree::cache::state::attributes::Source::IdMappingThenWorktree, + self.attributes, + )? + .0, + fs: capabilities, thread_limit, destination_is_initially_empty: false, overwrite_existing: false, keep_going: false, - trust_ctime: boolean(self, "core.trustCTime", &Core::TRUST_C_TIME, true)?, - check_stat: self - .apply_leniency( - self.resolved - .string("core", None, "checkStat") - .map(|v| Core::CHECK_STAT.try_into_checkstat(v)), - )? - .unwrap_or(true), - attribute_globals: assemble_attribute_globals(self, git_dir)?, + stat_options: gix_index::entry::stat::Options { + trust_ctime: boolean(self, "core.trustCTime", &Core::TRUST_C_TIME, true)?, + use_nsec: boolean(self, "gitoxide.core.useNsec", &gitoxide::Core::USE_NSEC, false)?, + use_stdev: boolean(self, "gitoxide.core.useStdev", &gitoxide::Core::USE_STDEV, false)?, + check_stat: self + .apply_leniency( + self.resolved + .string("core", None, "checkStat") + .map(|v| Core::CHECK_STAT.try_into_checkstat(v)), + )? + .unwrap_or(true), + }, }) } + + pub(crate) fn assemble_exclude_globals( + &self, + git_dir: &std::path::Path, + overrides: Option, + source: gix_worktree::cache::state::ignore::Source, + buf: &mut Vec, + ) -> Result { + let excludes_file = match self.excludes_file().transpose()? { + Some(user_path) => Some(user_path), + None => self.xdg_config_path("ignore")?, + }; + Ok(gix_worktree::cache::state::Ignore::new( + overrides.unwrap_or_default(), + gix_ignore::Search::from_git_dir(git_dir, excludes_file, buf)?, + None, + source, + )) + } + // TODO: at least one test, maybe related to core.attributesFile configuration. + pub(crate) fn assemble_attribute_globals( + &self, + git_dir: &std::path::Path, + source: gix_worktree::cache::state::attributes::Source, + attributes: crate::open::permissions::Attributes, + ) -> Result<(gix_worktree::cache::state::Attributes, Vec), config::attribute_stack::Error> { + let configured_or_user_attributes = match self + .trusted_file_path("core", None, Core::ATTRIBUTES_FILE.name) + .transpose()? + { + Some(attributes) => Some(attributes), + None => { + if attributes.git { + self.xdg_config_path("attributes").ok().flatten().map(Cow::Owned) + } else { + None + } + } + }; + let attribute_files = [gix_attributes::Source::GitInstallation, gix_attributes::Source::System] + .into_iter() + .filter(|source| match source { + Source::GitInstallation => attributes.git_binary, + Source::System => attributes.system, + Source::Git | Source::Local => unreachable!("we don't offer turning this off right now"), + }) + .filter_map(|source| source.storage_location(&mut Self::make_source_env(self.environment))) + .chain(configured_or_user_attributes); + let info_attributes_path = git_dir.join("info").join("attributes"); + let mut buf = Vec::new(); + let mut collection = gix_attributes::search::MetadataCollection::default(); + let res = gix_worktree::cache::state::Attributes::new( + gix_attributes::Search::new_globals(attribute_files, &mut buf, &mut collection)?, + Some(info_attributes_path), + source, + collection, + ); + Ok((res, buf)) + } + pub(crate) fn xdg_config_path( &self, resource_file_name: &str, ) -> Result, gix_sec::permission::Error> { std::env::var_os("XDG_CONFIG_HOME") - .map(|path| (PathBuf::from(path), &self.xdg_config_home_env)) + .map(|path| (PathBuf::from(path), &self.environment.xdg_config_home)) .or_else(|| { - std::env::var_os("HOME").map(|path| { + gix_path::env::home_dir().map(|mut p| { ( { - let mut p = PathBuf::from(path); p.push(".config"); p }, - &self.home_env, + &self.environment.home, ) }) }) @@ -226,8 +282,6 @@ impl Cache { /// We never fail for here even if the permission is set to deny as we `gix-config` will fail later /// if it actually wants to use the home directory - we don't want to fail prematurely. pub(crate) fn home_dir(&self) -> Option { - std::env::var_os("HOME") - .map(PathBuf::from) - .and_then(|path| self.home_env.check_opt(path)) + gix_path::env::home_dir().and_then(|path| self.environment.home.check_opt(path)) } } diff --git a/vendor/gix/src/config/cache/incubate.rs b/vendor/gix/src/config/cache/incubate.rs index 047f2132b..44c537b50 100644 --- a/vendor/gix/src/config/cache/incubate.rs +++ b/vendor/gix/src/config/cache/incubate.rs @@ -30,6 +30,7 @@ impl StageOne { gix_config::Source::Local, git_dir_trust, lossy, + lenient, )?; // Note that we assume the repo is bare by default unless we are told otherwise. This is relevant if @@ -64,6 +65,7 @@ impl StageOne { gix_config::Source::Worktree, git_dir_trust, lossy, + lenient, )?; config.append(worktree_config); }; @@ -86,24 +88,48 @@ fn load_config( source: gix_config::Source, git_dir_trust: gix_sec::Trust, lossy: Option, + lenient: bool, ) -> Result, Error> { - buf.clear(); let metadata = gix_config::file::Metadata::from(source) .at(&config_path) .with(git_dir_trust); let mut file = match std::fs::File::open(&config_path) { Ok(f) => f, Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(gix_config::File::new(metadata)), - Err(err) => return Err(err.into()), + Err(err) => { + let err = Error::Io { + source: err, + path: config_path, + }; + if lenient { + log::warn!("ignoring: {err:#?}"); + return Ok(gix_config::File::new(metadata)); + } else { + return Err(err); + } + } + }; + + buf.clear(); + if let Err(err) = std::io::copy(&mut file, buf) { + let err = Error::Io { + source: err, + path: config_path, + }; + if lenient { + log::warn!("ignoring: {err:#?}"); + buf.clear(); + } else { + return Err(err); + } }; - std::io::copy(&mut file, buf)?; let config = gix_config::File::from_bytes_owned( buf, metadata, gix_config::file::init::Options { includes: gix_config::file::includes::Options::no_follow(), - ..util::base_options(lossy) + ..util::base_options(lossy, lenient) }, )?; diff --git a/vendor/gix/src/config/cache/init.rs b/vendor/gix/src/config/cache/init.rs index dc76f78bb..ee20e0354 100644 --- a/vendor/gix/src/config/cache/init.rs +++ b/vendor/gix/src/config/cache/init.rs @@ -1,5 +1,5 @@ #![allow(clippy::result_large_err)] -use std::borrow::Cow; +use std::{borrow::Cow, ffi::OsString}; use gix_sec::Permission; @@ -12,7 +12,7 @@ use crate::{ tree::{gitoxide, Core, Http}, Cache, }, - repository, + open, }; /// Initialization @@ -32,23 +32,24 @@ impl Cache { filter_config_section: fn(&gix_config::file::Metadata) -> bool, git_install_dir: Option<&std::path::Path>, home: Option<&std::path::Path>, - repository::permissions::Environment { + environment @ open::permissions::Environment { git_prefix, - home: home_env, - xdg_config_home: xdg_config_home_env, ssh_prefix: _, + xdg_config_home: _, + home: _, http_transport, identity, objects, - }: repository::permissions::Environment, - repository::permissions::Config { + }: open::permissions::Environment, + attributes: open::permissions::Attributes, + open::permissions::Config { git_binary: use_installation, system: use_system, git: use_git, user: use_user, env: use_env, includes: use_includes, - }: repository::permissions::Config, + }: open::permissions::Config, lenient_config: bool, api_config_overrides: &[BString], cli_config_overrides: &[BString], @@ -65,12 +66,10 @@ impl Cache { } else { gix_config::file::includes::Options::no_follow() }, - ..util::base_options(lossy) + ..util::base_options(lossy, lenient_config) }; let config = { - let home_env = &home_env; - let xdg_config_home_env = &xdg_config_home_env; let git_prefix = &git_prefix; let metas = [ gix_config::source::Kind::GitInstallation, @@ -88,15 +87,7 @@ impl Cache { _ => {} } source - .storage_location(&mut |name| { - match name { - git_ if git_.starts_with("GIT_") => Some(git_prefix), - "XDG_CONFIG_HOME" => Some(xdg_config_home_env), - "HOME" => Some(home_env), - _ => None, - } - .and_then(|perm| perm.check_opt(name).and_then(std::env::var_os)) - }) + .storage_location(&mut Self::make_source_env(environment)) .map(|p| (source, p.into_owned())) }) .map(|(source, path)| gix_config::file::Metadata { @@ -118,7 +109,7 @@ impl Cache { ) .map_err(|err| match err { gix_config::file::init::from_paths::Error::Init(err) => Error::from(err), - gix_config::file::init::from_paths::Error::Io(err) => err.into(), + gix_config::file::init::from_paths::Error::Io { source, path } => Error::Io { source, path }, })? .unwrap_or_default(); @@ -175,9 +166,9 @@ impl Cache { ignore_case, hex_len, filter_config_section, - xdg_config_home_env, - home_env, + environment, lenient_config, + attributes, user_agent: Default::default(), personas: Default::default(), url_rewrite: Default::default(), @@ -240,6 +231,31 @@ impl Cache { Ok(()) } + + pub(crate) fn make_source_env( + crate::open::permissions::Environment { + xdg_config_home, + git_prefix, + home, + .. + }: open::permissions::Environment, + ) -> impl FnMut(&str) -> Option { + move |name| { + match name { + git_ if git_.starts_with("GIT_") => Some(git_prefix), + "XDG_CONFIG_HOME" => Some(xdg_config_home), + "HOME" => { + return if home.is_allowed() { + gix_path::env::home_dir().map(Into::into) + } else { + None + } + } + _ => None, + } + .and_then(|perm| perm.check_opt(name).and_then(gix_path::env::var)) + } + } } impl crate::Repository { @@ -347,6 +363,15 @@ fn apply_environment_overrides( }, ], ), + ( + "gitoxide", + Some(Cow::Borrowed("core".into())), + git_prefix, + &[{ + let key = &gitoxide::Core::SHALLOW_FILE; + (env(key), key.name) + }], + ), ( "gitoxide", Some(Cow::Borrowed("author".into())), diff --git a/vendor/gix/src/config/cache/util.rs b/vendor/gix/src/config/cache/util.rs index c12f850e6..d5a0a4acb 100644 --- a/vendor/gix/src/config/cache/util.rs +++ b/vendor/gix/src/config/cache/util.rs @@ -17,9 +17,10 @@ pub(crate) fn interpolate_context<'a>( } } -pub(crate) fn base_options(lossy: Option) -> gix_config::file::init::Options<'static> { +pub(crate) fn base_options(lossy: Option, lenient: bool) -> gix_config::file::init::Options<'static> { gix_config::file::init::Options { lossy: lossy.unwrap_or(!cfg!(debug_assertions)), + ignore_io_errors: lenient, ..Default::default() } } diff --git a/vendor/gix/src/config/mod.rs b/vendor/gix/src/config/mod.rs index 1e2566777..5da569605 100644 --- a/vendor/gix/src/config/mod.rs +++ b/vendor/gix/src/config/mod.rs @@ -62,8 +62,11 @@ pub enum Error { UnsupportedObjectFormat { name: BString }, #[error(transparent)] CoreAbbrev(#[from] abbrev::Error), - #[error("Could not read configuration file")] - Io(#[from] std::io::Error), + #[error("Could not read configuration file at \"{}\"", path.display())] + Io { + source: std::io::Error, + path: std::path::PathBuf, + }, #[error(transparent)] Init(#[from] gix_config::file::init::Error), #[error(transparent)] @@ -110,6 +113,36 @@ pub mod checkout_options { ConfigBoolean(#[from] super::boolean::Error), #[error(transparent)] CheckoutWorkers(#[from] super::checkout::workers::Error), + #[error(transparent)] + Attributes(#[from] super::attribute_stack::Error), + } +} + +/// +pub mod exclude_stack { + use std::path::PathBuf; + + /// The error produced when setting up a stack to query `gitignore` information. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Could not read repository exclude")] + Io(#[from] std::io::Error), + #[error(transparent)] + EnvironmentPermission(#[from] gix_sec::permission::Error), + #[error("The value for `core.excludesFile` could not be read from configuration")] + ExcludesFilePathInterpolation(#[from] gix_config::path::interpolate::Error), + } +} + +/// +pub mod attribute_stack { + /// The error produced when setting up the attribute stack to query `gitattributes`. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("An attribute file could not be read")] + Io(#[from] std::io::Error), #[error("Failed to interpolate the attribute file configured at `core.attributesFile`")] AttributesFileInterpolation(#[from] gix_config::path::interpolate::Error), } @@ -247,7 +280,7 @@ pub mod checkout { pub mod workers { use crate::config; - /// The error produced when failing to parse the the `checkout.workers` key. + /// The error produced when failing to parse the `checkout.workers` key. pub type Error = config::key::Error; } } @@ -446,9 +479,7 @@ pub(crate) struct Cache { /// If true, we should default what's possible if something is misconfigured, on case by case basis, to be more resilient. /// Also available in options! Keep in sync! pub lenient_config: bool, - /// Define how we can use values obtained with `xdg_config(…)` and its `XDG_CONFIG_HOME` variable. - xdg_config_home_env: gix_sec::Permission, - /// Define how we can use values obtained with `xdg_config(…)`. and its `HOME` variable. - home_env: gix_sec::Permission, + attributes: crate::open::permissions::Attributes, + environment: crate::open::permissions::Environment, // TODO: make core.precomposeUnicode available as well. } diff --git a/vendor/gix/src/config/tree/mod.rs b/vendor/gix/src/config/tree/mod.rs index fd769f3ed..b378b8c49 100644 --- a/vendor/gix/src/config/tree/mod.rs +++ b/vendor/gix/src/config/tree/mod.rs @@ -38,6 +38,8 @@ pub(crate) mod root { pub const GITOXIDE: sections::Gitoxide = sections::Gitoxide; /// The `http` section. pub const HTTP: sections::Http = sections::Http; + /// The `index` section. + pub const INDEX: sections::Index = sections::Index; /// The `init` section. pub const INIT: sections::Init = sections::Init; /// The `pack` section. @@ -69,6 +71,7 @@ pub(crate) mod root { &Self::EXTENSIONS, &Self::GITOXIDE, &Self::HTTP, + &Self::INDEX, &Self::INIT, &Self::PACK, &Self::PROTOCOL, @@ -84,9 +87,9 @@ pub(crate) mod root { mod sections; pub use sections::{ - branch, checkout, core, credential, diff, extensions, gitoxide, http, protocol, remote, ssh, Author, Branch, - Checkout, Clone, Committer, Core, Credential, Diff, Extensions, Gitoxide, Http, Init, Pack, Protocol, Remote, Safe, - Ssh, Url, User, + branch, checkout, core, credential, diff, extensions, gitoxide, http, index, protocol, remote, ssh, Author, Branch, + Checkout, Clone, Committer, Core, Credential, Diff, Extensions, Gitoxide, Http, Index, Init, Pack, Protocol, + Remote, Safe, Ssh, Url, User, }; /// Generic value implementations for static instantiation. diff --git a/vendor/gix/src/config/tree/sections/clone.rs b/vendor/gix/src/config/tree/sections/clone.rs index 616185a0b..6cb274e7d 100644 --- a/vendor/gix/src/config/tree/sections/clone.rs +++ b/vendor/gix/src/config/tree/sections/clone.rs @@ -7,6 +7,8 @@ impl Clone { /// The `clone.defaultRemoteName` key. pub const DEFAULT_REMOTE_NAME: keys::RemoteName = keys::RemoteName::new_remote_name("defaultRemoteName", &config::Tree::CLONE); + /// The `clone.rejectShallow` key. + pub const REJECT_SHALLOW: keys::Boolean = keys::Boolean::new_boolean("rejectShallow", &config::Tree::CLONE); } impl Section for Clone { @@ -15,6 +17,6 @@ impl Section for Clone { } fn keys(&self) -> &[&dyn Key] { - &[&Self::DEFAULT_REMOTE_NAME] + &[&Self::DEFAULT_REMOTE_NAME, &Self::REJECT_SHALLOW] } } diff --git a/vendor/gix/src/config/tree/sections/gitoxide.rs b/vendor/gix/src/config/tree/sections/gitoxide.rs index 8c3defd0b..7d60f1287 100644 --- a/vendor/gix/src/config/tree/sections/gitoxide.rs +++ b/vendor/gix/src/config/tree/sections/gitoxide.rs @@ -1,10 +1,15 @@ -use crate::config::tree::{keys, Gitoxide, Key, Section}; +use crate::{ + config, + config::tree::{keys, Gitoxide, Key, Section}, +}; impl Gitoxide { /// The `gitoxide.allow` section. pub const ALLOW: Allow = Allow; /// The `gitoxide.author` section. pub const AUTHOR: Author = Author; + /// The `gitoxide.core` section. + pub const CORE: Core = Core; /// The `gitoxide.commit` section. pub const COMMIT: Commit = Commit; /// The `gitoxide.committer` section. @@ -39,6 +44,7 @@ impl Section for Gitoxide { &[ &Self::ALLOW, &Self::AUTHOR, + &Self::CORE, &Self::COMMIT, &Self::COMMITTER, &Self::HTTP, @@ -56,6 +62,41 @@ mod subsections { Tree, }; + /// The `Core` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Core; + + impl Core { + /// The `gitoxide.core.useNsec` key. + pub const USE_NSEC: keys::Boolean = keys::Boolean::new_boolean("useNsec", &Gitoxide::CORE) + .with_note("A runtime version of the USE_NSEC build flag."); + + /// The `gitoxide.core.useStdev` key. + pub const USE_STDEV: keys::Boolean = keys::Boolean::new_boolean("useStdev", &Gitoxide::CORE) + .with_note("A runtime version of the USE_STDEV build flag."); + + /// The `gitoxide.core.shallowFile` key. + pub const SHALLOW_FILE: keys::Path = keys::Path::new_path("shallowFile", &Gitoxide::CORE) + .with_environment_override("GIT_SHALLOW_FILE") + .with_deviation( + "relative file paths will always be made relative to the git-common-dir, whereas `git` keeps them as is.", + ); + } + + impl Section for Core { + fn name(&self) -> &str { + "core" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::USE_NSEC, &Self::USE_STDEV, &Self::SHALLOW_FILE] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + /// The `Http` sub-section. #[derive(Copy, Clone, Default)] pub struct Http; @@ -341,6 +382,7 @@ mod subsections { } } } +pub use subsections::{Allow, Author, Commit, Committer, Core, Http, Https, Objects, Ssh, User}; pub mod validate { use std::error::Error; @@ -357,7 +399,3 @@ pub mod validate { } } } - -pub use subsections::{Allow, Author, Commit, Committer, Http, Https, Objects, Ssh, User}; - -use crate::config; diff --git a/vendor/gix/src/config/tree/sections/index.rs b/vendor/gix/src/config/tree/sections/index.rs new file mode 100644 index 000000000..d03322247 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/index.rs @@ -0,0 +1,62 @@ +use crate::{ + config, + config::tree::{keys, Index, Key, Section}, +}; + +impl Index { + /// The `index.threads` key. + pub const THREADS: IndexThreads = + IndexThreads::new_with_validate("threads", &config::Tree::INDEX, validate::IndexThreads); +} + +/// The `index.threads` key. +pub type IndexThreads = keys::Any; + +mod index_threads { + use crate::bstr::BStr; + use crate::config; + use crate::config::key::GenericErrorWithValue; + use crate::config::tree::index::IndexThreads; + use std::borrow::Cow; + + impl IndexThreads { + /// Parse `value` into the amount of threads to use, with `1` being single-threaded, or `0` indicating + /// to select the amount of threads, with any other number being the specific amount of threads to use. + pub fn try_into_index_threads( + &'static self, + value: Cow<'_, BStr>, + ) -> Result { + gix_config::Integer::try_from(value.as_ref()) + .ok() + .and_then(|i| i.to_decimal().and_then(|i| i.try_into().ok())) + .or_else(|| { + gix_config::Boolean::try_from(value.as_ref()) + .ok() + .map(|b| if b.0 { 0 } else { 1 }) + }) + .ok_or_else(|| GenericErrorWithValue::from_value(self, value.into_owned())) + } + } +} + +impl Section for Index { + fn name(&self) -> &str { + "index" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::THREADS] + } +} + +mod validate { + use crate::{bstr::BStr, config::tree::keys}; + + pub struct IndexThreads; + impl keys::Validate for IndexThreads { + fn validate(&self, value: &BStr) -> Result<(), Box> { + super::Index::THREADS.try_into_index_threads(value.into())?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/mod.rs b/vendor/gix/src/config/tree/sections/mod.rs index fb9b50786..9f0a50c93 100644 --- a/vendor/gix/src/config/tree/sections/mod.rs +++ b/vendor/gix/src/config/tree/sections/mod.rs @@ -55,6 +55,11 @@ pub mod gitoxide; pub struct Http; pub mod http; +/// The `index` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Index; +pub mod index; + /// The `init` top-level section. #[derive(Copy, Clone, Default)] pub struct Init; diff --git a/vendor/gix/src/create.rs b/vendor/gix/src/create.rs index 96d047e3b..878ec1164 100644 --- a/vendor/gix/src/create.rs +++ b/vendor/gix/src/create.rs @@ -117,7 +117,7 @@ pub struct Options { pub destination_must_be_empty: bool, /// If set, use these filesystem capabilities to populate the respective gix-config fields. /// If `None`, the directory will be probed. - pub fs_capabilities: Option, + pub fs_capabilities: Option, } /// Create a new `.git` repository of `kind` within the possibly non-existing `directory` @@ -208,7 +208,7 @@ pub fn into( { let mut config = gix_config::File::default(); { - let caps = fs_capabilities.unwrap_or_else(|| gix_worktree::fs::Capabilities::probe(&dot_git)); + let caps = fs_capabilities.unwrap_or_else(|| gix_fs::Capabilities::probe(&dot_git)); let mut core = config.new_section("core", None).expect("valid section name"); core.push(key("repositoryformatversion"), Some("0".into())); diff --git a/vendor/gix/src/diff.rs b/vendor/gix/src/diff.rs new file mode 100644 index 000000000..b10819293 --- /dev/null +++ b/vendor/gix/src/diff.rs @@ -0,0 +1,17 @@ +pub use gix_diff::*; + +/// +pub mod rename { + /// Determine how to do rename tracking. + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub enum Tracking { + /// Do not track renames at all, the fastest option. + Disabled, + /// Track renames. + Renames, + /// Track renames and copies. + /// + /// This is the most expensive option. + RenamesAndCopies, + } +} diff --git a/vendor/gix/src/id.rs b/vendor/gix/src/id.rs index c57565fb5..72f424334 100644 --- a/vendor/gix/src/id.rs +++ b/vendor/gix/src/id.rs @@ -90,9 +90,6 @@ impl<'repo> Id<'repo> { impl<'repo> Id<'repo> { /// Obtain a platform for traversing ancestors of this commit. /// - /// Note that unless [`error_on_missing_commit()`][revision::Walk::error_on_missing_commit()] is enabled, which be default it is not, - /// one will always see an empty iteration even if this id is not a commit, instead of an error. - /// If this is undesirable, it's best to check for the correct object type before creating an iterator. pub fn ancestors(&self) -> revision::walk::Platform<'repo> { revision::walk::Platform::new(Some(self.inner), self.repo) } @@ -186,10 +183,11 @@ mod tests { #[test] fn size_of_oid() { - assert_eq!( - std::mem::size_of::>(), - 32, - "size of oid shouldn't change without notice" + let actual = std::mem::size_of::>(); + let ceiling = 32; + assert!( + actual <= ceiling, + "size of oid shouldn't change without notice: {actual} <= {ceiling}" ) } } diff --git a/vendor/gix/src/lib.rs b/vendor/gix/src/lib.rs index 257a613d7..eb5efcfdf 100644 --- a/vendor/gix/src/lib.rs +++ b/vendor/gix/src/lib.rs @@ -73,8 +73,10 @@ pub use gix_date as date; pub use gix_features as features; use gix_features::threading::OwnShared; pub use gix_features::{parallel, progress::Progress, threading}; +pub use gix_fs as fs; pub use gix_glob as glob; pub use gix_hash as hash; +pub use gix_ignore as ignore; #[doc(inline)] pub use gix_index as index; pub use gix_lock as lock; @@ -92,18 +94,17 @@ pub use gix_traverse as traverse; pub use gix_url as url; #[doc(inline)] pub use gix_url::Url; +pub use gix_utils as utils; pub use hash::{oid, ObjectId}; pub mod interrupt; -mod ext; /// -pub mod prelude { - pub use gix_features::parallel::reduce::Finalize; - pub use gix_odb::{Find, FindExt, Header, HeaderExt, Write}; +pub mod attributes; - pub use crate::ext::*; -} +mod ext; +/// +pub mod prelude; /// pub mod path; @@ -133,33 +134,21 @@ mod repository; pub mod tag; /// -pub mod progress { - #[cfg(feature = "progress-tree")] - pub use gix_features::progress::prodash::tree; - pub use gix_features::progress::*; -} +pub mod progress; /// -pub mod diff { - pub use gix_diff::*; - /// - pub mod rename { - /// Determine how to do rename tracking. - #[derive(Debug, Copy, Clone, Eq, PartialEq)] - pub enum Tracking { - /// Do not track renames at all, the fastest option. - Disabled, - /// Track renames. - Renames, - /// Track renames and copies. - /// - /// This is the most expensive option. - RenamesAndCopies, - } - } -} +pub mod diff; /// See [ThreadSafeRepository::discover()], but returns a [`Repository`] instead. +/// +/// # Note +/// +/// **The discovered repository might not be suitable for any operation that requires authentication with remotes** +/// as it doesn't see the relevant git configuration. +/// +/// To achieve that, one has to [enable `git_binary` configuration](https://github.com/Byron/gitoxide/blob/9723e1addf52cc336d59322de039ea0537cdca36/src/plumbing/main.rs#L86) +/// in the open-options and use [`ThreadSafeRepository::discover_opts()`] instead. Alternatively, it might be well-known +/// that the tool is going to run in a neatly configured environment without relying on bundled configuration. #[allow(clippy::result_large_err)] pub fn discover(directory: impl AsRef) -> Result { ThreadSafeRepository::discover(directory).map(Into::into) @@ -237,23 +226,6 @@ pub fn open_opts(directory: impl Into, options: open::Option ThreadSafeRepository::open_opts(directory, options).map(Into::into) } -/// -pub mod permission { - /// - pub mod env_var { - /// - pub mod resource { - /// - pub type Error = gix_sec::permission::Error; - } - } -} -/// -pub mod permissions { - pub use crate::repository::permissions::{Config, Environment}; -} -pub use repository::permissions::Permissions; - /// pub mod create; @@ -278,33 +250,10 @@ pub mod remote; pub mod init; /// Not to be confused with 'status'. -pub mod state { - /// Tell what operation is currently in progress. - #[derive(Debug, PartialEq, Eq)] - pub enum InProgress { - /// A mailbox is being applied. - ApplyMailbox, - /// A rebase is happening while a mailbox is being applied. - // TODO: test - ApplyMailboxRebase, - /// A git bisect operation has not yet been concluded. - Bisect, - /// A cherry pick operation. - CherryPick, - /// A cherry pick with multiple commits pending. - CherryPickSequence, - /// A merge operation. - Merge, - /// A rebase operation. - Rebase, - /// An interactive rebase operation. - RebaseInteractive, - /// A revert operation. - Revert, - /// A revert operation with multiple commits pending. - RevertSequence, - } -} +pub mod state; + +/// +pub mod shallow; /// pub mod discover; diff --git a/vendor/gix/src/object/commit.rs b/vendor/gix/src/object/commit.rs index e28a12955..5a9dfd4f3 100644 --- a/vendor/gix/src/object/commit.rs +++ b/vendor/gix/src/object/commit.rs @@ -109,7 +109,7 @@ impl<'repo> Commit<'repo> { .map(move |id| id.attach(repo)) } - /// Parse the commit and return the the tree object it points to. + /// Parse the commit and return the tree object it points to. pub fn tree(&self) -> Result, Error> { match self.tree_id()?.object()?.try_into_tree() { Ok(tree) => Ok(tree), @@ -117,7 +117,7 @@ impl<'repo> Commit<'repo> { } } - /// Parse the commit and return the the tree id it points to. + /// Parse the commit and return the tree id it points to. pub fn tree_id(&self) -> Result, gix_object::decode::Error> { gix_object::CommitRefIter::from_bytes(&self.data) .tree_id() diff --git a/vendor/gix/src/object/mod.rs b/vendor/gix/src/object/mod.rs index 75d77d138..d0a37db6c 100644 --- a/vendor/gix/src/object/mod.rs +++ b/vendor/gix/src/object/mod.rs @@ -90,6 +90,14 @@ impl<'repo> Object<'repo> { } } + /// Transform this object into a tag, or panic if it is none. + pub fn into_tag(self) -> Tag<'repo> { + match self.try_into() { + Ok(tag) => tag, + Err(this) => panic!("Tried to use {} as commit, but was {}", this.id, this.kind), + } + } + /// Transform this object into a commit, or return it as part of the `Err` if it is no commit. pub fn try_into_commit(self) -> Result, try_into::Error> { self.try_into().map_err(|this: Self| try_into::Error { @@ -157,7 +165,7 @@ impl<'repo> Object<'repo> { }) } - /// Obtain a an iterator over commit tokens like in [`to_commit_iter()`][Object::try_to_commit_ref_iter()]. + /// Obtain an iterator over commit tokens like in [`to_commit_iter()`][Object::try_to_commit_ref_iter()]. /// /// # Panic /// diff --git a/vendor/gix/src/object/tag.rs b/vendor/gix/src/object/tag.rs index ce9d7360a..77eaaa259 100644 --- a/vendor/gix/src/object/tag.rs +++ b/vendor/gix/src/object/tag.rs @@ -1,6 +1,17 @@ use crate::{ext::ObjectIdExt, Tag}; impl<'repo> Tag<'repo> { + /// Decode the entire tag object and return it for accessing all tag information. + /// + /// This never allocates. + /// + /// Note that the returned commit object does make lookup easy and should be + /// used for successive calls to string-ish information to avoid decoding the object + /// more than once. + pub fn decode(&self) -> Result, gix_object::decode::Error> { + gix_object::TagRef::from_bytes(&self.data) + } + /// Decode this tag partially and return the id of its target. pub fn target_id(&self) -> Result, gix_object::decode::Error> { gix_object::TagRefIter::from_bytes(&self.data) diff --git a/vendor/gix/src/object/tree/diff/mod.rs b/vendor/gix/src/object/tree/diff/mod.rs index 5a3bf6ddf..447eeaa84 100644 --- a/vendor/gix/src/object/tree/diff/mod.rs +++ b/vendor/gix/src/object/tree/diff/mod.rs @@ -3,20 +3,15 @@ use gix_diff::tree::recorder::Location; use crate::{bstr::BStr, Tree}; /// Returned by the `for_each` function to control flow. -#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] +#[derive(Default, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] pub enum Action { /// Continue the traversal of changes. + #[default] Continue, /// Stop the traversal of changes and stop calling this function. Cancel, } -impl Default for Action { - fn default() -> Self { - Action::Continue - } -} - /// Represents any possible change in order to turn one tree into another. #[derive(Debug, Clone, Copy)] pub struct Change<'a, 'old, 'new> { diff --git a/vendor/gix/src/object/tree/mod.rs b/vendor/gix/src/object/tree/mod.rs index db094bcb9..bbd392289 100644 --- a/vendor/gix/src/object/tree/mod.rs +++ b/vendor/gix/src/object/tree/mod.rs @@ -22,6 +22,11 @@ impl<'repo> Tree<'repo> { Id::from_id(self.id, self.repo) } + /// Parse our tree data and return the parse tree for direct access to its entries. + pub fn decode(&self) -> Result, gix_object::decode::Error> { + gix_object::TreeRef::from_bytes(&self.data) + } + // TODO: tests. /// Follow a sequence of `path` components starting from this instance, and look them up one by one until the last component /// is looked up and its tree entry is returned. @@ -156,3 +161,15 @@ mod entry { } } } + +mod _impls { + use crate::Tree; + + impl TryFrom> for gix_object::Tree { + type Error = gix_object::decode::Error; + + fn try_from(t: Tree<'_>) -> Result { + t.decode().map(Into::into) + } + } +} diff --git a/vendor/gix/src/open/mod.rs b/vendor/gix/src/open/mod.rs index 77018f5a2..03c976204 100644 --- a/vendor/gix/src/open/mod.rs +++ b/vendor/gix/src/open/mod.rs @@ -1,6 +1,17 @@ use std::path::PathBuf; -use crate::{bstr::BString, config, permission, Permissions}; +use crate::{bstr::BString, config}; + +/// Permissions associated with various resources of a git repository +#[derive(Debug, Clone)] +pub struct Permissions { + /// Control which environment variables may be accessed. + pub env: permissions::Environment, + /// Permissions related where git configuration should be loaded from. + pub config: permissions::Config, + /// Permissions related to where `gitattributes` should be loaded from. + pub attributes: permissions::Attributes, +} /// The options used in [`ThreadSafeRepository::open_opts()`][crate::ThreadSafeRepository::open_opts()]. /// @@ -16,7 +27,7 @@ pub struct Options { /// Define what is allowed while opening a repository. pub permissions: Permissions, pub(crate) git_dir_trust: Option, - /// Warning: this one is copied to to config::Cache - don't change it after repo open or keep in sync. + /// Warning: this one is copied to config::Cache - don't change it after repo open or keep in sync. pub(crate) filter_config_section: Option bool>, pub(crate) lossy_config: Option, pub(crate) lenient_config: bool, @@ -44,11 +55,11 @@ pub enum Error { #[error("The git directory at '{}' is considered unsafe as it's not owned by the current user.", .path.display())] UnsafeGitDir { path: PathBuf }, #[error(transparent)] - EnvironmentAccessDenied(#[from] permission::env_var::resource::Error), + EnvironmentAccessDenied(#[from] gix_sec::permission::Error), } mod options; - +pub mod permissions; mod repository; #[cfg(test)] diff --git a/vendor/gix/src/open/options.rs b/vendor/gix/src/open/options.rs index fb648e3c2..b098d55c1 100644 --- a/vendor/gix/src/open/options.rs +++ b/vendor/gix/src/open/options.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use super::{Error, Options}; -use crate::{bstr::BString, config, Permissions, ThreadSafeRepository}; +use crate::{bstr::BString, config, open::Permissions, ThreadSafeRepository}; impl Default for Options { fn default() -> Self { @@ -134,6 +134,9 @@ impl Options { /// /// This is recommended for all applications that prefer correctness over usability. /// `git` itself defaults to strict configuration mode, flagging incorrect configuration immediately. + /// + /// Failure to read configuration files due to IO errors will also be a hard error if this mode is enabled, otherwise + /// these errors will merely be logged. pub fn strict_config(mut self, toggle: bool) -> Self { self.lenient_config = !toggle; self diff --git a/vendor/gix/src/open/permissions.rs b/vendor/gix/src/open/permissions.rs new file mode 100644 index 000000000..633575a9d --- /dev/null +++ b/vendor/gix/src/open/permissions.rs @@ -0,0 +1,215 @@ +//! Various permissions to define what can be done when operating a [`Repository`][crate::Repository]. +use crate::open::Permissions; +use gix_sec::Trust; + +/// Configure from which sources git configuration may be loaded. +/// +/// Note that configuration from inside of the repository is always loaded as it's definitely required for correctness. +#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] +pub struct Config { + /// The git binary may come with configuration as part of its configuration, and if this is true (default false) + /// we will load the configuration of the git binary, if present and not a duplicate of the ones below. + /// + /// It's disabled by default as it may involve executing the git binary once per execution of the application. + pub git_binary: bool, + /// Whether to use the system configuration. + /// This is defined as `$(prefix)/etc/gitconfig` on unix. + pub system: bool, + /// Whether to use the git application configuration. + /// + /// A platform defined location for where a user's git application configuration should be located. + /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/config` will be used + /// on unix. + pub git: bool, + /// Whether to use the user configuration. + /// This is usually `~/.gitconfig` on unix. + pub user: bool, + /// Whether to use the configuration from environment variables. + pub env: bool, + /// Whether to follow include files are encountered in loaded configuration, + /// via `include` and `includeIf` sections. + pub includes: bool, +} + +impl Config { + /// Allow everything which usually relates to a fully trusted environment + pub fn all() -> Self { + Config { + git_binary: false, + system: true, + git: true, + user: true, + env: true, + includes: true, + } + } + + /// Load only configuration local to the git repository. + pub fn isolated() -> Self { + Config { + git_binary: false, + system: false, + git: false, + user: false, + env: false, + includes: false, + } + } +} + +impl Default for Config { + fn default() -> Self { + Self::all() + } +} + +/// Configure from which `gitattribute` files may be loaded. +/// +/// Note that `.gitattribute` files from within the repository are always loaded. +#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] +pub struct Attributes { + /// The git binary may come with attribute configuration in its installation directory, and if this is true (default false) + /// we will load the configuration of the git binary. + /// + /// It's disabled by default as it involves executing the git binary once per execution of the application. + pub git_binary: bool, + /// Whether to use the system configuration. + /// This is typically defined as `$(prefix)/etc/gitconfig`. + pub system: bool, + /// Whether to use the git application configuration. + /// + /// A platform defined location for where a user's git application configuration should be located. + /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/attributes` will be used + /// on unix. + pub git: bool, +} + +impl Attributes { + /// Allow everything which usually relates to a fully trusted environment + pub fn all() -> Self { + Attributes { + git_binary: false, + system: true, + git: true, + } + } + + /// Allow loading attributes that are local to the git repository. + pub fn isolated() -> Self { + Attributes { + git_binary: false, + system: false, + git: false, + } + } +} + +impl Default for Attributes { + fn default() -> Self { + Self::all() + } +} + +/// Permissions related to the usage of environment variables +#[derive(Debug, Clone, Copy)] +pub struct Environment { + /// Control whether resources pointed to by `XDG_CONFIG_HOME` can be used when looking up common configuration values. + /// + /// Note that [`gix_sec::Permission::Forbid`] will cause the operation to abort if a resource is set via the XDG config environment. + pub xdg_config_home: gix_sec::Permission, + /// Control the way resources pointed to by the home directory (similar to `xdg_config_home`) may be used. + pub home: gix_sec::Permission, + /// Control if environment variables to configure the HTTP transport, like `http_proxy` may be used. + /// + /// Note that http-transport related environment variables prefixed with `GIT_` may also be included here + /// if they match this category like `GIT_HTTP_USER_AGENT`. + pub http_transport: gix_sec::Permission, + /// Control if the `EMAIL` environment variables may be read. + /// + /// Note that identity related environment variables prefixed with `GIT_` may also be included here + /// if they match this category. + pub identity: gix_sec::Permission, + /// Control if environment variables related to the object database are handled. This includes features and performance + /// options alike. + pub objects: gix_sec::Permission, + /// Control if resources pointed to by `GIT_*` prefixed environment variables can be used, **but only** if they + /// are not contained in any other category. This is a catch-all section. + pub git_prefix: gix_sec::Permission, + /// Control if resources pointed to by `SSH_*` prefixed environment variables can be used (like `SSH_ASKPASS`) + pub ssh_prefix: gix_sec::Permission, +} + +impl Environment { + /// Allow access to the entire environment. + pub fn all() -> Self { + let allow = gix_sec::Permission::Allow; + Environment { + xdg_config_home: allow, + home: allow, + git_prefix: allow, + ssh_prefix: allow, + http_transport: allow, + identity: allow, + objects: allow, + } + } + + /// Don't allow loading any environment variables. + pub fn isolated() -> Self { + let deny = gix_sec::Permission::Deny; + Environment { + xdg_config_home: deny, + home: deny, + ssh_prefix: deny, + git_prefix: deny, + http_transport: deny, + identity: deny, + objects: deny, + } + } +} + +impl Permissions { + /// Secure permissions are similar to `all()` + pub fn secure() -> Self { + Permissions { + env: Environment::all(), + config: Config::all(), + attributes: Attributes::all(), + } + } + + /// Everything is allowed with this set of permissions, thus we read all configuration and do what git typically + /// does with owned repositories. + pub fn all() -> Self { + Permissions { + env: Environment::all(), + config: Config::all(), + attributes: Attributes::all(), + } + } + + /// Don't read any but the local git configuration and deny reading any environment variables. + pub fn isolated() -> Self { + Permissions { + config: Config::isolated(), + attributes: Attributes::isolated(), + env: Environment::isolated(), + } + } +} + +impl gix_sec::trust::DefaultForLevel for Permissions { + fn default_for_level(level: Trust) -> Self { + match level { + Trust::Full => Permissions::all(), + Trust::Reduced => Permissions::secure(), + } + } +} + +impl Default for Permissions { + fn default() -> Self { + Permissions::secure() + } +} diff --git a/vendor/gix/src/open/repository.rs b/vendor/gix/src/open/repository.rs index 85dd91da7..c7702b5f6 100644 --- a/vendor/gix/src/open/repository.rs +++ b/vendor/gix/src/open/repository.rs @@ -10,7 +10,8 @@ use crate::{ cache::{interpolate_context, util::ApplyLeniency}, tree::{gitoxide, Core, Key, Safe}, }, - permission, Permissions, ThreadSafeRepository, + open::Permissions, + ThreadSafeRepository, }; #[derive(Default, Clone)] @@ -26,7 +27,7 @@ pub(crate) struct EnvironmentOverrides { } impl EnvironmentOverrides { - fn from_env() -> Result { + fn from_env() -> Result> { let mut worktree_dir = None; if let Some(path) = std::env::var_os(Core::WORKTREE.the_environment_override()) { worktree_dir = PathBuf::from(path).into(); @@ -146,13 +147,18 @@ impl ThreadSafeRepository { lenient_config, bail_if_untrusted, open_path_as_is: _, - permissions: Permissions { ref env, config }, + permissions: + Permissions { + ref env, + config, + attributes, + }, ref api_config_overrides, ref cli_config_overrides, ref current_dir, } = options; let current_dir = current_dir.as_deref().expect("BUG: current_dir must be set by caller"); - let git_dir_trust = git_dir_trust.expect("trust must be been determined by now"); + let git_dir_trust = git_dir_trust.expect("trust must be determined by now"); // TODO: assure we handle the worktree-dir properly as we can have config per worktree with an extension. // This would be something read in later as have to first check for extensions. Also this means @@ -180,9 +186,7 @@ impl ThreadSafeRepository { }; let head = refs.find("HEAD").ok(); let git_install_dir = crate::path::install_dir().ok(); - let home = std::env::var_os("HOME") - .map(PathBuf::from) - .and_then(|home| env.home.check_opt(home)); + let home = gix_path::env::home_dir().and_then(|home| env.home.check_opt(home)); let mut filter_config_section = filter_config_section.unwrap_or(config::section::is_trusted); let config = config::Cache::from_stage_one( @@ -192,7 +196,8 @@ impl ThreadSafeRepository { filter_config_section, git_install_dir.as_deref(), home.as_deref(), - env.clone(), + *env, + attributes, config, lenient_config, api_config_overrides, @@ -266,7 +271,8 @@ impl ThreadSafeRepository { config, // used when spawning new repositories off this one when following worktrees linked_worktree_options: options, - index: gix_features::fs::MutableSnapshot::new().into(), + index: gix_fs::SharedFileSnapshotMut::new().into(), + shallow_commits: gix_fs::SharedFileSnapshotMut::new().into(), }) } } diff --git a/vendor/gix/src/prelude.rs b/vendor/gix/src/prelude.rs new file mode 100644 index 000000000..36fbfc7b1 --- /dev/null +++ b/vendor/gix/src/prelude.rs @@ -0,0 +1,4 @@ +pub use gix_features::parallel::reduce::Finalize; +pub use gix_odb::{Find, FindExt, Header, HeaderExt, Write}; + +pub use crate::ext::*; diff --git a/vendor/gix/src/progress.rs b/vendor/gix/src/progress.rs new file mode 100644 index 000000000..0a88aa044 --- /dev/null +++ b/vendor/gix/src/progress.rs @@ -0,0 +1,3 @@ +#[cfg(feature = "progress-tree")] +pub use gix_features::progress::prodash::tree; +pub use gix_features::progress::*; diff --git a/vendor/gix/src/remote/connect.rs b/vendor/gix/src/remote/connect.rs index 8e656975e..3dbd93486 100644 --- a/vendor/gix/src/remote/connect.rs +++ b/vendor/gix/src/remote/connect.rs @@ -1,7 +1,7 @@ #![allow(clippy::result_large_err)] use gix_protocol::transport::client::Transport; -use crate::{remote::Connection, Progress, Remote}; +use crate::{remote::Connection, Remote}; mod error { use crate::{bstr::BString, config, remote}; @@ -51,17 +51,15 @@ impl<'repo> Remote<'repo> { /// /// Note that this method expects the `transport` to be created by the user, which would involve the [`url()`][Self::url()]. /// It's meant to be used when async operation is needed with runtimes of the user's choice. - pub fn to_connection_with_transport(&self, transport: T, progress: P) -> Connection<'_, 'repo, T, P> + pub fn to_connection_with_transport(&self, transport: T) -> Connection<'_, 'repo, T> where T: Transport, - P: Progress, { Connection { remote: self, authenticate: None, transport_options: None, transport, - progress, } } @@ -75,14 +73,10 @@ impl<'repo> Remote<'repo> { /// [to_connection_with_transport()][Self::to_connection_with_transport()]. #[cfg(any(feature = "blocking-network-client", feature = "async-network-client-async-std"))] #[gix_protocol::maybe_async::maybe_async] - pub async fn connect

( + pub async fn connect( &self, direction: crate::remote::Direction, - progress: P, - ) -> Result, P>, Error> - where - P: Progress, - { + ) -> Result>, Error> { let (url, version) = self.sanitized_url_and_version(direction)?; #[cfg(feature = "blocking-network-client")] let scheme_is_ssh = url.scheme == gix_url::Scheme::Ssh; @@ -98,7 +92,7 @@ impl<'repo> Remote<'repo> { }, ) .await?; - Ok(self.to_connection_with_transport(transport, progress)) + Ok(self.to_connection_with_transport(transport)) } /// Produce the sanitized URL and protocol version to use as obtained by querying the repository configuration. diff --git a/vendor/gix/src/remote/connection/access.rs b/vendor/gix/src/remote/connection/access.rs index e4c31c3f5..eba603da0 100644 --- a/vendor/gix/src/remote/connection/access.rs +++ b/vendor/gix/src/remote/connection/access.rs @@ -4,7 +4,7 @@ use crate::{ }; /// Builder -impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> { +impl<'a, 'repo, T> Connection<'a, 'repo, T> { /// Set a custom credentials callback to provide credentials if the remotes require authentication. /// /// Otherwise we will use the git configuration to perform the same task as the `git credential` helper program, @@ -37,8 +37,26 @@ impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> { } } +/// Mutation +impl<'a, 'repo, T> Connection<'a, 'repo, T> { + /// Like [`with_credentials()`][Self::with_credentials()], but without consuming the connection. + pub fn set_credentials( + &mut self, + helper: impl FnMut(gix_credentials::helper::Action) -> gix_credentials::protocol::Result + 'a, + ) -> &mut Self { + self.authenticate = Some(Box::new(helper)); + self + } + + /// Like [`with_transport_options()`][Self::with_transport_options()], but without consuming the connection. + pub fn set_transport_options(&mut self, config: Box) -> &mut Self { + self.transport_options = Some(config); + self + } +} + /// Access -impl<'a, 'repo, T, P> Connection<'a, 'repo, T, P> { +impl<'a, 'repo, T> Connection<'a, 'repo, T> { /// A utility to return a function that will use this repository's configuration to obtain credentials, similar to /// what `git credential` is doing. /// diff --git a/vendor/gix/src/remote/connection/fetch/error.rs b/vendor/gix/src/remote/connection/fetch/error.rs index 0e6a4b840..afcacca13 100644 --- a/vendor/gix/src/remote/connection/fetch/error.rs +++ b/vendor/gix/src/remote/connection/fetch/error.rs @@ -28,6 +28,21 @@ pub enum Error { path: std::path::PathBuf, source: std::io::Error, }, + #[error(transparent)] + ShallowOpen(#[from] crate::shallow::open::Error), + #[error("Server lack feature {feature:?}: {description}")] + MissingServerFeature { + feature: &'static str, + description: &'static str, + }, + #[error("Could not write 'shallow' file to incorporate remote updates after fetching")] + WriteShallowFile(#[from] crate::shallow::write::Error), + #[error("'shallow' file could not be locked in preparation for writing changes")] + LockShallowFile(#[from] gix_lock::acquire::Error), + #[error("Could not obtain configuration to learn if shallow remotes should be rejected")] + RejectShallowRemoteConfig(#[from] config::boolean::Error), + #[error("Receiving objects from shallow remotes is prohibited due to the value of `clone.rejectShallow`")] + RejectShallowRemote, } impl gix_protocol::transport::IsSpuriousError for Error { diff --git a/vendor/gix/src/remote/connection/fetch/mod.rs b/vendor/gix/src/remote/connection/fetch/mod.rs index 4ce631b1e..a51ae7c54 100644 --- a/vendor/gix/src/remote/connection/fetch/mod.rs +++ b/vendor/gix/src/remote/connection/fetch/mod.rs @@ -116,10 +116,9 @@ pub mod prepare { } } -impl<'remote, 'repo, T, P> Connection<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Connection<'remote, 'repo, T> where T: Transport, - P: Progress, { /// Perform a handshake with the remote and obtain a ref-map with `options`, and from there one /// Note that at this point, the `transport` should already be configured using the [`transport_mut()`][Self::transport_mut()] @@ -137,23 +136,25 @@ where #[gix_protocol::maybe_async::maybe_async] pub async fn prepare_fetch( mut self, + progress: impl Progress, options: ref_map::Options, - ) -> Result, prepare::Error> { + ) -> Result, prepare::Error> { if self.remote.refspecs(remote::Direction::Fetch).is_empty() { return Err(prepare::Error::MissingRefSpecs); } - let ref_map = self.ref_map_inner(options).await?; + let ref_map = self.ref_map_inner(progress, options).await?; Ok(Prepare { con: Some(self), ref_map, dry_run: DryRun::No, reflog_message: None, write_packed_refs: WritePackedRefs::Never, + shallow: Default::default(), }) } } -impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Prepare<'remote, 'repo, T> where T: Transport, { @@ -170,19 +171,20 @@ mod receive_pack; pub mod refs; /// A structure to hold the result of the handshake with the remote and configure the upcoming fetch operation. -pub struct Prepare<'remote, 'repo, T, P> +pub struct Prepare<'remote, 'repo, T> where T: Transport, { - con: Option>, + con: Option>, ref_map: RefMap, dry_run: DryRun, reflog_message: Option, write_packed_refs: WritePackedRefs, + shallow: remote::fetch::Shallow, } /// Builder -impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Prepare<'remote, 'repo, T> where T: Transport, { @@ -212,9 +214,17 @@ where self.reflog_message = reflog_message.into(); self } + + /// Define what to do when the current repository is a shallow clone. + /// + /// *Has no effect if the current repository is not as shallow clone.* + pub fn with_shallow(mut self, shallow: remote::fetch::Shallow) -> Self { + self.shallow = shallow; + self + } } -impl<'remote, 'repo, T, P> Drop for Prepare<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Drop for Prepare<'remote, 'repo, T> where T: Transport, { diff --git a/vendor/gix/src/remote/connection/fetch/negotiate.rs b/vendor/gix/src/remote/connection/fetch/negotiate.rs index f5051ec72..771c5acba 100644 --- a/vendor/gix/src/remote/connection/fetch/negotiate.rs +++ b/vendor/gix/src/remote/connection/fetch/negotiate.rs @@ -1,3 +1,5 @@ +use crate::remote::fetch; + /// The way the negotiation is performed #[derive(Copy, Clone)] pub(crate) enum Algorithm { @@ -16,6 +18,7 @@ pub enum Error { /// Negotiate one round with `algo` by looking at `ref_map` and adjust `arguments` to contain the haves and wants. /// If this is not the first round, the `previous_response` is set with the last recorded server response. /// Returns `true` if the negotiation is done from our side so the server won't keep asking. +#[allow(clippy::too_many_arguments)] pub(crate) fn one_round( algo: Algorithm, round: usize, @@ -24,13 +27,20 @@ pub(crate) fn one_round( fetch_tags: crate::remote::fetch::Tags, arguments: &mut gix_protocol::fetch::Arguments, _previous_response: Option<&gix_protocol::fetch::Response>, + shallow: Option<&fetch::Shallow>, ) -> Result { let tag_refspec_to_ignore = fetch_tags .to_refspec() .filter(|_| matches!(fetch_tags, crate::remote::fetch::Tags::Included)); + if let Some(fetch::Shallow::Deepen(0)) = shallow { + // Avoid deepening (relative) with zero as it seems to upset the server. Git also doesn't actually + // perform the negotiation for some reason (couldn't find it in code). + return Ok(true); + } + match algo { Algorithm::Naive => { - assert_eq!(round, 1, "Naive always finishes after the first round, and claims."); + assert_eq!(round, 1, "Naive always finishes after the first round, it claims."); let mut has_missing_tracking_branch = false; for mapping in &ref_map.mappings { if tag_refspec_to_ignore.map_or(false, |tag_spec| { @@ -65,10 +75,11 @@ pub(crate) fn one_round( } } - if has_missing_tracking_branch { + if has_missing_tracking_branch || (shallow.is_some() && arguments.is_empty()) { if let Ok(Some(r)) = repo.head_ref() { if let Some(id) = r.target().try_id() { arguments.have(id); + arguments.want(id); } } } diff --git a/vendor/gix/src/remote/connection/fetch/receive_pack.rs b/vendor/gix/src/remote/connection/fetch/receive_pack.rs index 686de5999..99560fbca 100644 --- a/vendor/gix/src/remote/connection/fetch/receive_pack.rs +++ b/vendor/gix/src/remote/connection/fetch/receive_pack.rs @@ -1,23 +1,25 @@ -use std::sync::atomic::AtomicBool; +use std::sync::atomic::{AtomicBool, Ordering}; use gix_odb::FindExt; -use gix_protocol::transport::client::Transport; +use gix_protocol::{ + fetch::Arguments, + transport::{client::Transport, packetline::read::ProgressAction}, +}; use crate::{ + config::tree::Clone, remote, remote::{ connection::fetch::config, fetch, - fetch::{negotiate, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Status}, + fetch::{negotiate, refs, Error, Outcome, Prepare, ProgressId, RefLogMessage, Shallow, Status}, }, - Progress, + Progress, Repository, }; -impl<'remote, 'repo, T, P> Prepare<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Prepare<'remote, 'repo, T> where T: Transport, - P: Progress, - P::SubProgress: 'static, { /// Receive the pack and perform the operation as configured by git via `gix-config` or overridden by various builder methods. /// Return `Ok(None)` if there was nothing to do because all remote refs are at the same state as they are locally, or `Ok(Some(outcome))` @@ -62,14 +64,18 @@ where /// - `gitoxide.userAgent` is read to obtain the application user agent for git servers and for HTTP servers as well. /// #[gix_protocol::maybe_async::maybe_async] - pub async fn receive(mut self, should_interrupt: &AtomicBool) -> Result { + pub async fn receive

(mut self, mut progress: P, should_interrupt: &AtomicBool) -> Result + where + P: Progress, + P::SubProgress: 'static, + { let mut con = self.con.take().expect("receive() can only be called once"); let handshake = &self.ref_map.handshake; let protocol_version = handshake.server_protocol_version; let fetch = gix_protocol::Command::Fetch; - let progress = &mut con.progress; + let progress = &mut progress; let repo = con.remote.repo; let fetch_features = { let mut f = fetch.default_features(protocol_version, &handshake.capabilities); @@ -82,10 +88,17 @@ where let mut arguments = gix_protocol::fetch::Arguments::new(protocol_version, fetch_features); if matches!(con.remote.fetch_tags, crate::remote::fetch::Tags::Included) { if !arguments.can_use_include_tag() { - unimplemented!("we expect servers to support 'include-tag', otherwise we have to implement another pass to fetch attached tags separately"); + return Err(Error::MissingServerFeature { + feature: "include-tag", + description: + // NOTE: if this is an issue, we could probably do what's proposed here. + "To make this work we would have to implement another pass to fetch attached tags separately", + }); } arguments.use_include_tag(); } + let (shallow_commits, mut shallow_lock) = add_shallow_args(&mut arguments, &self.shallow, repo)?; + let mut previous_response = None::; let mut round = 1; @@ -108,6 +121,7 @@ where con.remote.fetch_tags, &mut arguments, previous_response.as_ref(), + (self.shallow != Shallow::NoChange).then_some(&self.shallow), ) { Ok(_) if arguments.is_empty() => { gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); @@ -137,20 +151,35 @@ where round += 1; let mut reader = arguments.send(&mut con.transport, is_done).await?; if sideband_all { - setup_remote_progress(progress, &mut reader); + setup_remote_progress(progress, &mut reader, should_interrupt); } let response = gix_protocol::fetch::Response::from_line_reader(protocol_version, &mut reader).await?; if response.has_pack() { progress.step(); progress.set_name("receiving pack"); if !sideband_all { - setup_remote_progress(progress, &mut reader); + setup_remote_progress(progress, &mut reader, should_interrupt); } + previous_response = Some(response); break 'negotiation reader; } else { previous_response = Some(response); } }; + let previous_response = previous_response.expect("knowledge of a pack means a response was received"); + if !previous_response.shallow_updates().is_empty() && shallow_lock.is_none() { + let reject_shallow_remote = repo + .config + .resolved + .boolean_filter_by_key("clone.rejectShallow", &mut repo.filter_config_section()) + .map(|val| Clone::REJECT_SHALLOW.enrich_error(val)) + .transpose()? + .unwrap_or(false); + if reject_shallow_remote { + return Err(Error::RejectShallowRemote); + } + shallow_lock = acquire_shallow_lock(repo).map(Some)?; + } let options = gix_pack::bundle::write::Options { thread_limit: config::index_threads(repo)?, @@ -170,7 +199,7 @@ where reader }, Some(repo.objects.store_ref().path().join("pack")), - con.progress, + progress, should_interrupt, Some(Box::new({ let repo = repo.clone(); @@ -187,6 +216,12 @@ where gix_protocol::indicate_end_of_interaction(&mut con.transport).await.ok(); } + if let Some(shallow_lock) = shallow_lock { + if !previous_response.shallow_updates().is_empty() { + crate::shallow::write(shallow_lock, shallow_commits, previous_response.shallow_updates())?; + } + } + let update_refs = refs::update( repo, self.reflog_message @@ -221,9 +256,61 @@ where } } +fn acquire_shallow_lock(repo: &Repository) -> Result { + gix_lock::File::acquire_to_update_resource(repo.shallow_file(), gix_lock::acquire::Fail::Immediately, None) + .map_err(Into::into) +} + +fn add_shallow_args( + args: &mut Arguments, + shallow: &Shallow, + repo: &Repository, +) -> Result<(Option, Option), Error> { + let expect_change = *shallow != Shallow::NoChange; + let shallow_lock = expect_change.then(|| acquire_shallow_lock(repo)).transpose()?; + + let shallow_commits = repo.shallow_commits()?; + if (shallow_commits.is_some() || expect_change) && !args.can_use_shallow() { + // NOTE: if this is an issue, we can always unshallow the repo ourselves. + return Err(Error::MissingServerFeature { + feature: "shallow", + description: "shallow clones need server support to remain shallow, otherwise bigger than expected packs are sent effectively unshallowing the repository", + }); + } + if let Some(shallow_commits) = &shallow_commits { + for commit in shallow_commits.iter() { + args.shallow(commit); + } + } + match shallow { + Shallow::NoChange => {} + Shallow::DepthAtRemote(commits) => args.deepen(commits.get() as usize), + Shallow::Deepen(commits) => { + args.deepen(*commits as usize); + args.deepen_relative(); + } + Shallow::Since { cutoff } => { + args.deepen_since(cutoff.seconds_since_unix_epoch as usize); + } + Shallow::Exclude { + remote_refs, + since_cutoff, + } => { + if let Some(cutoff) = since_cutoff { + args.deepen_since(cutoff.seconds_since_unix_epoch as usize); + } + for ref_ in remote_refs { + args.deepen_not(ref_.as_ref().as_bstr()); + } + } + } + Ok((shallow_commits, shallow_lock)) +} + fn setup_remote_progress

( progress: &mut P, reader: &mut Box, + should_interrupt: &AtomicBool, ) where P: Progress, P::SubProgress: 'static, @@ -231,8 +318,23 @@ fn setup_remote_progress

( use gix_protocol::transport::client::ExtendedBufRead; reader.set_progress_handler(Some(Box::new({ let mut remote_progress = progress.add_child_with_id("remote", ProgressId::RemoteProgress.into()); + // SAFETY: Ugh, so, with current Rust I can't declare lifetimes in the involved traits the way they need to + // be and I also can't use scoped threads to pump from local scopes to an Arc version that could be + // used here due to the this being called from sync AND async code (and the async version doesn't work + // with a surrounding `std::thread::scope()`. + // Thus there is only claiming this is 'static which we know works for *our* implementations of ExtendedBufRead + // and typical implementations, but of course it's possible for user code to come along and actually move this + // handler into a context where it can outlive the current function. Is this going to happen? Probably not unless + // somebody really wants to break it. So, with standard usage this value is never used past its actual lifetime. + #[allow(unsafe_code)] + let should_interrupt: &'static AtomicBool = unsafe { std::mem::transmute(should_interrupt) }; move |is_err: bool, data: &[u8]| { - gix_protocol::RemoteProgress::translate_to_progress(is_err, data, &mut remote_progress) + gix_protocol::RemoteProgress::translate_to_progress(is_err, data, &mut remote_progress); + if should_interrupt.load(Ordering::Relaxed) { + ProgressAction::Interrupt + } else { + ProgressAction::Continue + } } }) as gix_protocol::transport::client::HandleProgress)); } diff --git a/vendor/gix/src/remote/connection/mod.rs b/vendor/gix/src/remote/connection/mod.rs index 09943ecc4..02a09926a 100644 --- a/vendor/gix/src/remote/connection/mod.rs +++ b/vendor/gix/src/remote/connection/mod.rs @@ -12,12 +12,11 @@ pub type AuthenticateFn<'a> = Box /// /// It can be used to perform a variety of operations with the remote without worrying about protocol details, /// much like a remote procedure call. -pub struct Connection<'a, 'repo, T, P> { +pub struct Connection<'a, 'repo, T> { pub(crate) remote: &'a Remote<'repo>, pub(crate) authenticate: Option>, pub(crate) transport_options: Option>, pub(crate) transport: T, - pub(crate) progress: P, } mod access; diff --git a/vendor/gix/src/remote/connection/ref_map.rs b/vendor/gix/src/remote/connection/ref_map.rs index 0206e9002..abf9c8e00 100644 --- a/vendor/gix/src/remote/connection/ref_map.rs +++ b/vendor/gix/src/remote/connection/ref_map.rs @@ -71,10 +71,9 @@ impl Default for Options { } } -impl<'remote, 'repo, T, P> Connection<'remote, 'repo, T, P> +impl<'remote, 'repo, T> Connection<'remote, 'repo, T> where T: Transport, - P: Progress, { /// List all references on the remote that have been filtered through our remote's [`refspecs`][crate::Remote::refspecs()] /// for _fetching_. @@ -94,8 +93,8 @@ where /// - `gitoxide.userAgent` is read to obtain the application user agent for git servers and for HTTP servers as well. #[allow(clippy::result_large_err)] #[gix_protocol::maybe_async::maybe_async] - pub async fn ref_map(mut self, options: Options) -> Result { - let res = self.ref_map_inner(options).await; + pub async fn ref_map(mut self, progress: impl Progress, options: Options) -> Result { + let res = self.ref_map_inner(progress, options).await; gix_protocol::indicate_end_of_interaction(&mut self.transport) .await .ok(); @@ -106,6 +105,7 @@ where #[gix_protocol::maybe_async::maybe_async] pub(crate) async fn ref_map_inner( &mut self, + progress: impl Progress, Options { prefix_from_spec_as_filter_on_remote, handshake_parameters, @@ -125,7 +125,12 @@ where s }; let remote = self - .fetch_refs(prefix_from_spec_as_filter_on_remote, handshake_parameters, &specs) + .fetch_refs( + prefix_from_spec_as_filter_on_remote, + handshake_parameters, + &specs, + progress, + ) .await?; let num_explicit_specs = self.remote.fetch_specs.len(); let group = gix_refspec::MatchGroup::from_fetch_specs(specs.iter().map(|s| s.to_ref())); @@ -179,6 +184,7 @@ where filter_by_prefix: bool, extra_parameters: Vec<(String, Option)>, refspecs: &[gix_refspec::RefSpec], + mut progress: impl Progress, ) -> Result { let mut credentials_storage; let url = self.transport.to_url(); @@ -209,8 +215,7 @@ where self.transport.configure(&**config)?; } let mut outcome = - gix_protocol::fetch::handshake(&mut self.transport, authenticate, extra_parameters, &mut self.progress) - .await?; + gix_protocol::fetch::handshake(&mut self.transport, authenticate, extra_parameters, &mut progress).await?; let refs = match outcome.refs.take() { Some(refs) => refs, None => { @@ -236,7 +241,7 @@ where } Ok(gix_protocol::ls_refs::Action::Continue) }, - &mut self.progress, + &mut progress, ) .await? } diff --git a/vendor/gix/src/remote/fetch.rs b/vendor/gix/src/remote/fetch.rs index 4add96a65..0001447cb 100644 --- a/vendor/gix/src/remote/fetch.rs +++ b/vendor/gix/src/remote/fetch.rs @@ -19,24 +19,19 @@ pub(crate) enum WritePackedRefs { } /// Describe how to handle tags when fetching -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] pub enum Tags { /// Fetch all tags from the remote, even if these are not reachable from objects referred to by our refspecs. All, /// Fetch only the tags that point to the objects being sent. /// That way, annotated tags that point to an object we receive are automatically transmitted and their refs are created. /// The same goes for lightweight tags. + #[default] Included, /// Do not fetch any tags. None, } -impl Default for Tags { - fn default() -> Self { - Tags::Included - } -} - impl Tags { /// Obtain a refspec that determines whether or not to fetch all tags, depending on this variant. /// @@ -52,6 +47,47 @@ impl Tags { } } +/// Describe how shallow clones are handled when fetching, with variants defining how the *shallow boundary* is handled. +/// +/// The *shallow boundary* is a set of commits whose parents are not present in the repository. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub enum Shallow { + /// Fetch all changes from the remote without affecting the shallow boundary at all. + #[default] + NoChange, + /// Receive update to `depth` commits in the history of the refs to fetch (from the viewpoint of the remote), + /// with the value of `1` meaning to receive only the commit a ref is pointing to. + /// + /// This may update the shallow boundary to increase or decrease the amount of available history. + DepthAtRemote(std::num::NonZeroU32), + /// Increase the number of commits and thus expand the shallow boundary by `depth` commits as seen from our local + /// shallow boundary, with a value of `0` having no effect. + Deepen(u32), + /// Set the shallow boundary at the `cutoff` time, meaning that there will be no commits beyond that time. + Since { + /// The date beyond which there will be no history. + cutoff: gix_date::Time, + }, + /// Receive all history excluding all commits reachable from `remote_refs`. These can be long or short + /// ref names or tag names. + Exclude { + /// The ref names to exclude, short or long. Note that ambiguous short names will cause the remote to abort + /// without an error message being transferred (because the protocol does not support it) + remote_refs: Vec, + /// If some, this field has the same meaning as [`Shallow::Since`] which can be used in combination + /// with excluded references. + since_cutoff: Option, + }, +} + +impl Shallow { + /// Produce a variant that causes the repository to loose its shallow boundary, effectively by extending it + /// beyond all limits. + pub fn undo() -> Self { + Shallow::DepthAtRemote((i32::MAX as u32).try_into().expect("valid at compile time")) + } +} + /// Information about the relationship between our refspecs, and remote references with their local counterparts. #[derive(Default, Debug, Clone)] #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] diff --git a/vendor/gix/src/remote/save.rs b/vendor/gix/src/remote/save.rs index 0e347551e..ad6a75b14 100644 --- a/vendor/gix/src/remote/save.rs +++ b/vendor/gix/src/remote/save.rs @@ -32,6 +32,7 @@ impl Remote<'_> { /// Note that all sections named `remote ""` will be cleared of all values we are about to write, /// and the last `remote ""` section will be containing all relevant values so that reloading the remote /// from `config` would yield the same in-memory state. + #[allow(clippy::result_large_err)] pub fn save_to(&self, config: &mut gix_config::File<'static>) -> Result<(), Error> { fn as_key(name: &str) -> gix_config::parse::section::Key<'_> { name.try_into().expect("valid") @@ -109,6 +110,7 @@ impl Remote<'_> { /// Note that this sets a name for anonymous remotes, but overwrites the name for those who were named before. /// If this name is different from the current one, the git configuration will still contain the previous name, /// and the caller should account for that. + #[allow(clippy::result_large_err)] pub fn save_as_to( &mut self, name: impl Into, diff --git a/vendor/gix/src/repository/attributes.rs b/vendor/gix/src/repository/attributes.rs new file mode 100644 index 000000000..252529761 --- /dev/null +++ b/vendor/gix/src/repository/attributes.rs @@ -0,0 +1,50 @@ +//! exclude information +use crate::Repository; + +impl Repository { + /// Configure a file-system cache for accessing git attributes *and* excludes on a per-path basis. + /// + /// Use `attribute_source` to specify where to read attributes from. Also note that exclude information will + /// always try to read `.gitignore` files from disk before trying to read it from the `index`. + /// + /// Note that no worktree is required for this to work, even though access to in-tree `.gitattributes` and `.gitignore` files + /// would require a non-empty `index` that represents a git tree. + /// + /// This takes into consideration all the usual repository configuration, namely: + /// + /// * `$XDG_CONFIG_HOME/…/ignore|attributes` if `core.excludesFile|attributesFile` is *not* set, otherwise use the configured file. + /// * `$GIT_DIR/info/exclude|attributes` if present. + // TODO: test, provide higher-level custom Cache wrapper that is much easier to use and doesn't panic when accessing entries + // by non-relative path. + pub fn attributes( + &self, + index: &gix_index::State, + attributes_source: gix_worktree::cache::state::attributes::Source, + ignore_source: gix_worktree::cache::state::ignore::Source, + exclude_overrides: Option, + ) -> Result { + let case = if self.config.ignore_case { + gix_glob::pattern::Case::Fold + } else { + gix_glob::pattern::Case::Sensitive + }; + let (attributes, mut buf) = self.config.assemble_attribute_globals( + self.git_dir(), + attributes_source, + self.options.permissions.attributes, + )?; + let ignore = + self.config + .assemble_exclude_globals(self.git_dir(), exclude_overrides, ignore_source, &mut buf)?; + let state = gix_worktree::cache::State::AttributesAndIgnoreStack { attributes, ignore }; + let attribute_list = state.id_mappings_from_index(index, index.path_backing(), ignore_source, case); + Ok(gix_worktree::Cache::new( + // this is alright as we don't cause mutation of that directory, it's virtual. + self.work_dir().unwrap_or(self.git_dir()), + state, + case, + buf, + attribute_list, + )) + } +} diff --git a/vendor/gix/src/repository/excludes.rs b/vendor/gix/src/repository/excludes.rs new file mode 100644 index 000000000..6281549e0 --- /dev/null +++ b/vendor/gix/src/repository/excludes.rs @@ -0,0 +1,45 @@ +//! exclude information +use crate::{config, Repository}; +impl Repository { + /// Configure a file-system cache checking if files below the repository are excluded, reading `.gitignore` files from + /// the specified `source`. + /// + /// Note that no worktree is required for this to work, even though access to in-tree `.gitignore` files would require + /// a non-empty `index` that represents a tree with `.gitignore` files. + /// + /// This takes into consideration all the usual repository configuration, namely: + /// + /// * `$XDG_CONFIG_HOME/…/ignore` if `core.excludesFile` is *not* set, otherwise use the configured file. + /// * `$GIT_DIR/info/exclude` if present. + /// + /// When only excludes are desired, this is the most efficient way to obtain them. Otherwise use + /// [`Repository::attributes()`] for accessing both attributes and excludes. + // TODO: test, provide higher-level custom Cache wrapper that is much easier to use and doesn't panic when accessing entries + // by non-relative path. + pub fn excludes( + &self, + index: &gix_index::State, + overrides: Option, + source: gix_worktree::cache::state::ignore::Source, + ) -> Result { + let case = if self.config.ignore_case { + gix_glob::pattern::Case::Fold + } else { + gix_glob::pattern::Case::Sensitive + }; + let mut buf = Vec::with_capacity(512); + let ignore = self + .config + .assemble_exclude_globals(self.git_dir(), overrides, source, &mut buf)?; + let state = gix_worktree::cache::State::IgnoreStack(ignore); + let attribute_list = state.id_mappings_from_index(index, index.path_backing(), source, case); + Ok(gix_worktree::Cache::new( + // this is alright as we don't cause mutation of that directory, it's virtual. + self.work_dir().unwrap_or(self.git_dir()), + state, + case, + buf, + attribute_list, + )) + } +} diff --git a/vendor/gix/src/repository/impls.rs b/vendor/gix/src/repository/impls.rs index 6cf2b2e9b..5da55290c 100644 --- a/vendor/gix/src/repository/impls.rs +++ b/vendor/gix/src/repository/impls.rs @@ -8,6 +8,7 @@ impl Clone for crate::Repository { self.config.clone(), self.options.clone(), self.index.clone(), + self.shallow_commits.clone(), ) } } @@ -40,6 +41,7 @@ impl From<&crate::ThreadSafeRepository> for crate::Repository { repo.config.clone(), repo.linked_worktree_options.clone(), repo.index.clone(), + repo.shallow_commits.clone(), ) } } @@ -54,6 +56,7 @@ impl From for crate::Repository { repo.config, repo.linked_worktree_options, repo.index, + repo.shallow_commits, ) } } @@ -68,6 +71,7 @@ impl From for crate::ThreadSafeRepository { config: r.config, linked_worktree_options: r.options, index: r.index, + shallow_commits: r.shallow_commits, } } } diff --git a/vendor/gix/src/repository/init.rs b/vendor/gix/src/repository/init.rs index ae6a42c3b..16659a013 100644 --- a/vendor/gix/src/repository/init.rs +++ b/vendor/gix/src/repository/init.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; impl crate::Repository { + #[allow(clippy::too_many_arguments)] pub(crate) fn from_refs_and_objects( refs: crate::RefStore, objects: crate::OdbHandle, @@ -9,6 +10,7 @@ impl crate::Repository { config: crate::config::Cache, linked_worktree_options: crate::open::Options, index: crate::worktree::IndexStorage, + shallow_commits: crate::shallow::CommitsStorage, ) -> Self { let objects = setup_objects(objects, &config); crate::Repository { @@ -20,6 +22,7 @@ impl crate::Repository { config, options: linked_worktree_options, index, + shallow_commits, } } diff --git a/vendor/gix/src/repository/mod.rs b/vendor/gix/src/repository/mod.rs index 31199e22d..5b7a70d3b 100644 --- a/vendor/gix/src/repository/mod.rs +++ b/vendor/gix/src/repository/mod.rs @@ -19,17 +19,19 @@ impl crate::Repository { } } +mod attributes; mod cache; mod config; +mod excludes; pub(crate) mod identity; mod impls; mod init; mod location; mod object; -pub(crate) mod permissions; mod reference; mod remote; mod revision; +mod shallow; mod snapshots; mod state; mod thread_safe; diff --git a/vendor/gix/src/repository/object.rs b/vendor/gix/src/repository/object.rs index bda1a54c3..f4592475f 100644 --- a/vendor/gix/src/repository/object.rs +++ b/vendor/gix/src/repository/object.rs @@ -1,5 +1,6 @@ #![allow(clippy::result_large_err)] use std::convert::TryInto; +use std::ops::DerefMut; use gix_hash::ObjectId; use gix_odb::{Find, FindExt, Write}; @@ -36,7 +37,7 @@ impl crate::Repository { Ok(Object::from_data(id, kind, buf, self)) } - /// Try to find the object with `id` or return `None` it it wasn't found. + /// Try to find the object with `id` or return `None` if it wasn't found. pub fn try_find_object(&self, id: impl Into) -> Result>, object::find::Error> { let id = id.into(); if id == gix_hash::ObjectId::empty_tree(self.object_hash()) { @@ -58,32 +59,71 @@ impl crate::Repository { } } + fn shared_empty_buf(&self) -> std::cell::RefMut<'_, Vec> { + let mut bufs = self.bufs.borrow_mut(); + if bufs.last().is_none() { + bufs.push(Vec::with_capacity(512)); + } + std::cell::RefMut::map(bufs, |bufs| { + let buf = bufs.last_mut().expect("we assure one is present"); + buf.clear(); + buf + }) + } + /// Write the given object into the object database and return its object id. + /// + /// Note that we hash the object in memory to avoid storing objects that are already present. That way, + /// we avoid writing duplicate objects using slow disks that will eventually have to be garbage collected. pub fn write_object(&self, object: impl gix_object::WriteTo) -> Result, object::write::Error> { + let mut buf = self.shared_empty_buf(); + object.write_to(buf.deref_mut())?; + + let oid = gix_object::compute_hash(self.object_hash(), object.kind(), &buf); + if self.objects.contains(oid) { + return Ok(oid.attach(self)); + } + self.objects - .write(object) + .write_buf(object.kind(), &buf) .map(|oid| oid.attach(self)) .map_err(Into::into) } /// Write a blob from the given `bytes`. + /// + /// We avoid writing duplicate objects to slow disks that will eventually have to be garbage collected by + /// pre-hashing the data, and checking if the object is already present. pub fn write_blob(&self, bytes: impl AsRef<[u8]>) -> Result, 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) { + return Ok(oid.attach(self)); + } self.objects - .write_buf(gix_object::Kind::Blob, bytes.as_ref()) + .write_buf(gix_object::Kind::Blob, bytes) .map(|oid| oid.attach(self)) } /// Write a blob from the given `Read` implementation. + /// + /// Note that we hash the object in memory to avoid storing objects that are already present. That way, + /// we avoid writing duplicate objects using slow disks that will eventually have to be garbage collected. + /// + /// If that is prohibitive, use the object database directly. pub fn write_blob_stream( &self, mut bytes: impl std::io::Read + std::io::Seek, ) -> Result, object::write::Error> { - let current = bytes.stream_position()?; - let len = bytes.seek(std::io::SeekFrom::End(0))? - current; - bytes.seek(std::io::SeekFrom::Start(current))?; + let mut buf = self.shared_empty_buf(); + std::io::copy(&mut bytes, buf.deref_mut())?; + let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, &buf); + if self.objects.contains(oid) { + return Ok(oid.attach(self)); + } self.objects - .write_stream(gix_object::Kind::Blob, len, bytes) + .write_buf(gix_object::Kind::Blob, &buf) .map(|oid| oid.attach(self)) } diff --git a/vendor/gix/src/repository/permissions.rs b/vendor/gix/src/repository/permissions.rs index 88b61b739..633575a9d 100644 --- a/vendor/gix/src/repository/permissions.rs +++ b/vendor/gix/src/repository/permissions.rs @@ -1,14 +1,7 @@ +//! Various permissions to define what can be done when operating a [`Repository`][crate::Repository]. +use crate::open::Permissions; use gix_sec::Trust; -/// Permissions associated with various resources of a git repository -#[derive(Debug, Clone)] -pub struct Permissions { - /// Permissions related to the environment - pub env: Environment, - /// Permissions related to the handling of git configuration. - pub config: Config, -} - /// Configure from which sources git configuration may be loaded. /// /// Note that configuration from inside of the repository is always loaded as it's definitely required for correctness. @@ -17,7 +10,7 @@ pub struct Config { /// The git binary may come with configuration as part of its configuration, and if this is true (default false) /// we will load the configuration of the git binary, if present and not a duplicate of the ones below. /// - /// It's disable by default as it involves executing the git binary once per execution of the application. + /// It's disabled by default as it may involve executing the git binary once per execution of the application. pub git_binary: bool, /// Whether to use the system configuration. /// This is defined as `$(prefix)/etc/gitconfig` on unix. @@ -50,6 +43,18 @@ impl Config { includes: true, } } + + /// Load only configuration local to the git repository. + pub fn isolated() -> Self { + Config { + git_binary: false, + system: false, + git: false, + user: false, + env: false, + includes: false, + } + } } impl Default for Config { @@ -58,8 +63,55 @@ impl Default for Config { } } +/// Configure from which `gitattribute` files may be loaded. +/// +/// Note that `.gitattribute` files from within the repository are always loaded. +#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] +pub struct Attributes { + /// The git binary may come with attribute configuration in its installation directory, and if this is true (default false) + /// we will load the configuration of the git binary. + /// + /// It's disabled by default as it involves executing the git binary once per execution of the application. + pub git_binary: bool, + /// Whether to use the system configuration. + /// This is typically defined as `$(prefix)/etc/gitconfig`. + pub system: bool, + /// Whether to use the git application configuration. + /// + /// A platform defined location for where a user's git application configuration should be located. + /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/attributes` will be used + /// on unix. + pub git: bool, +} + +impl Attributes { + /// Allow everything which usually relates to a fully trusted environment + pub fn all() -> Self { + Attributes { + git_binary: false, + system: true, + git: true, + } + } + + /// Allow loading attributes that are local to the git repository. + pub fn isolated() -> Self { + Attributes { + git_binary: false, + system: false, + git: false, + } + } +} + +impl Default for Attributes { + fn default() -> Self { + Self::all() + } +} + /// Permissions related to the usage of environment variables -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct Environment { /// Control whether resources pointed to by `XDG_CONFIG_HOME` can be used when looking up common configuration values. /// @@ -101,18 +153,29 @@ impl Environment { objects: allow, } } + + /// Don't allow loading any environment variables. + pub fn isolated() -> Self { + let deny = gix_sec::Permission::Deny; + Environment { + xdg_config_home: deny, + home: deny, + ssh_prefix: deny, + git_prefix: deny, + http_transport: deny, + identity: deny, + objects: deny, + } + } } impl Permissions { - /// Return permissions that will not include configuration files not owned by the current user, - /// but trust system and global configuration files along with those which are owned by the current user. - /// - /// This allows to read and write repositories even if they aren't owned by the current user, but avoid using - /// anything else that could cause us to write into unknown locations or use programs beyond our `PATH`. + /// Secure permissions are similar to `all()` pub fn secure() -> Self { Permissions { env: Environment::all(), config: Config::all(), + attributes: Attributes::all(), } } @@ -122,32 +185,16 @@ impl Permissions { Permissions { env: Environment::all(), config: Config::all(), + attributes: Attributes::all(), } } /// Don't read any but the local git configuration and deny reading any environment variables. pub fn isolated() -> Self { Permissions { - config: Config { - git_binary: false, - system: false, - git: false, - user: false, - env: false, - includes: false, - }, - env: { - let deny = gix_sec::Permission::Deny; - Environment { - xdg_config_home: deny, - home: deny, - ssh_prefix: deny, - git_prefix: deny, - http_transport: deny, - identity: deny, - objects: deny, - } - }, + config: Config::isolated(), + attributes: Attributes::isolated(), + env: Environment::isolated(), } } } diff --git a/vendor/gix/src/repository/shallow.rs b/vendor/gix/src/repository/shallow.rs new file mode 100644 index 000000000..7fac83a55 --- /dev/null +++ b/vendor/gix/src/repository/shallow.rs @@ -0,0 +1,65 @@ +use std::{borrow::Cow, path::PathBuf}; + +use crate::{ + bstr::ByteSlice, + config::tree::{gitoxide, Key}, + Repository, +}; + +impl Repository { + /// Return `true` if the repository is a shallow clone, i.e. contains history only up to a certain depth. + pub fn is_shallow(&self) -> bool { + self.shallow_file() + .metadata() + .map_or(false, |m| m.is_file() && m.len() > 0) + } + + /// Return a shared list of shallow commits which is updated automatically if the in-memory snapshot has become stale + /// as the underlying file on disk has changed. + /// + /// The list of shallow commits represents the shallow boundary, beyond which we are lacking all (parent) commits. + /// Note that the list is never empty, as `Ok(None)` is returned in that case indicating the repository + /// isn't a shallow clone. + /// + /// The shared list is shared across all clones of this repository. + pub fn shallow_commits(&self) -> Result, crate::shallow::open::Error> { + self.shallow_commits.recent_snapshot( + || self.shallow_file().metadata().ok().and_then(|m| m.modified().ok()), + || { + let buf = match std::fs::read(self.shallow_file()) { + Ok(buf) => buf, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(err) => return Err(err.into()), + }; + + let mut commits = buf + .lines() + .map(gix_hash::ObjectId::from_hex) + .collect::, _>>()?; + + commits.sort(); + if commits.is_empty() { + Ok(None) + } else { + Ok(Some(commits)) + } + }, + ) + } + + /// Return the path to the `shallow` file which contains hashes, one per line, that describe commits that don't have their + /// parents within this repository. + /// + /// Note that it may not exist if the repository isn't actually shallow. + pub fn shallow_file(&self) -> PathBuf { + let shallow_name = self + .config + .resolved + .string_filter_by_key( + gitoxide::Core::SHALLOW_FILE.logical_name().as_str(), + &mut self.filter_config_section(), + ) + .unwrap_or_else(|| Cow::Borrowed("shallow".into())); + self.common_dir().join(gix_path::from_bstr(shallow_name)) + } +} diff --git a/vendor/gix/src/repository/worktree.rs b/vendor/gix/src/repository/worktree.rs index 2de31bc86..316009d29 100644 --- a/vendor/gix/src/repository/worktree.rs +++ b/vendor/gix/src/repository/worktree.rs @@ -1,6 +1,7 @@ +use crate::config::cache::util::ApplyLeniencyDefault; use crate::{worktree, Worktree}; -/// Worktree iteration +/// Interact with individual worktrees and their information. impl crate::Repository { /// Return a list of all _linked_ worktrees sorted by private git dir path as a lightweight proxy. /// @@ -25,10 +26,6 @@ impl crate::Repository { res.sort_by(|a, b| a.git_dir.cmp(&b.git_dir)); Ok(res) } -} - -/// Interact with individual worktrees and their information. -impl crate::Repository { /// Return the repository owning the main worktree, typically from a linked worktree. /// /// Note that it might be the one that is currently open if this repository doesn't point to a linked worktree. @@ -58,23 +55,14 @@ impl crate::Repository { /// /// It will use the `index.threads` configuration key to learn how many threads to use. /// Note that it may fail if there is no index. - // TODO: test pub fn open_index(&self) -> Result { let thread_limit = self .config .resolved - .boolean("index", None, "threads") - .map(|res| { - res.map(|value| usize::from(!value)).or_else(|err| { - gix_config::Integer::try_from(err.input.as_ref()) - .map_err(|err| worktree::open_index::Error::ConfigIndexThreads { - value: err.input.clone(), - err, - }) - .map(|value| value.to_decimal().and_then(|v| v.try_into().ok()).unwrap_or(1)) - }) - }) - .transpose()?; + .string("index", None, "threads") + .map(|value| crate::config::tree::Index::THREADS.try_into_index_threads(value)) + .transpose() + .with_lenient_default(self.config.lenient_config)?; gix_index::File::at( self.index_path(), self.object_hash(), diff --git a/vendor/gix/src/revision/spec/parse/types.rs b/vendor/gix/src/revision/spec/parse/types.rs index 4e523ab14..d852c297e 100644 --- a/vendor/gix/src/revision/spec/parse/types.rs +++ b/vendor/gix/src/revision/spec/parse/types.rs @@ -1,7 +1,7 @@ use crate::{bstr::BString, object, reference}; /// A hint to know what to do if refs and object names are equal. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] pub enum RefsHint { /// This is the default, and leads to specs that look like objects identified by full hex sha and are objects to be used /// instead of similarly named references. The latter is not typical but can absolutely happen by accident. @@ -9,6 +9,7 @@ pub enum RefsHint { /// preferred as there are many valid object names like `beef` and `cafe` that are short and both valid and typical prefixes /// for objects. /// Git chooses this as default as well, even though it means that every object prefix is also looked up as ref. + #[default] PreferObjectOnFullLengthHexShaUseRefOtherwise, /// No matter what, if it looks like an object prefix and has an object, use it. /// Note that no ref-lookup is made here which is the fastest option. @@ -38,12 +39,6 @@ pub enum ObjectKindHint { Blob, } -impl Default for RefsHint { - fn default() -> Self { - RefsHint::PreferObjectOnFullLengthHexShaUseRefOtherwise - } -} - /// Options for use in [`revision::Spec::from_bstr()`][crate::revision::Spec::from_bstr()]. #[derive(Debug, Default, Copy, Clone)] pub struct Options { @@ -177,6 +172,8 @@ pub enum Error { }, #[error(transparent)] Traverse(#[from] gix_traverse::commit::ancestors::Error), + #[error(transparent)] + Walk(#[from] crate::revision::walk::Error), #[error("Spec does not contain a single object id")] SingleNotFound, } diff --git a/vendor/gix/src/revision/walk.rs b/vendor/gix/src/revision/walk.rs index 5b04b43a7..9c545d0d4 100644 --- a/vendor/gix/src/revision/walk.rs +++ b/vendor/gix/src/revision/walk.rs @@ -3,6 +3,16 @@ use gix_odb::FindExt; use crate::{revision, Repository}; +/// The error returned by [`Platform::all()`]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + AncestorIter(#[from] gix_traverse::commit::ancestors::Error), + #[error(transparent)] + ShallowCommits(#[from] crate::shallow::open::Error), +} + /// A platform to traverse the revision graph by adding starting points as well as points which shouldn't be crossed, /// returned by [`Repository::rev_walk()`]. pub struct Platform<'repo> { @@ -40,13 +50,14 @@ impl<'repo> Platform<'repo> { /// Produce the iterator impl<'repo> Platform<'repo> { - /// Return an iterator to traverse all commits reachable as configured by the [Platform]. - /// - /// # Performance + /// For each commit, let `filter` return `true` if it and its parents should be included in the traversal, or `false` + /// if the traversal should exclude it and its ancestry entirely. /// - /// It's highly recommended to set an [`object cache`][Repository::object_cache_size()] on the parent repo - /// to greatly speed up performance if the returned id is supposed to be looked up right after. - pub fn all(self) -> Result, gix_traverse::commit::ancestors::Error> { + /// If `filter` is None, no pruning of the graph will be performed which is the default. + pub fn selected( + self, + mut filter: impl FnMut(&gix_hash::oid) -> bool + 'repo, + ) -> Result, Error> { let Platform { repo, tips, @@ -56,18 +67,52 @@ impl<'repo> Platform<'repo> { Ok(revision::Walk { repo, inner: Box::new( - gix_traverse::commit::Ancestors::new( + gix_traverse::commit::Ancestors::filtered( tips, gix_traverse::commit::ancestors::State::default(), move |oid, buf| repo.objects.find_commit_iter(oid, buf), + { + let shallow_commits = repo.shallow_commits()?; + let mut grafted_parents_to_skip = Vec::new(); + let mut buf = Vec::new(); + move |id| { + if !filter(id) { + return false; + } + match shallow_commits.as_ref() { + Some(commits) => { + let id = id.to_owned(); + if let Ok(idx) = grafted_parents_to_skip.binary_search(&id) { + grafted_parents_to_skip.remove(idx); + return false; + }; + if commits.binary_search(&id).is_ok() { + if let Ok(commit) = repo.objects.find_commit_iter(id, &mut buf) { + grafted_parents_to_skip.extend(commit.parent_ids()); + grafted_parents_to_skip.sort(); + } + }; + true + } + None => true, + } + } + }, ) .sorting(sorting)? .parents(parents), ), - is_shallow: None, - error_on_missing_commit: false, }) } + /// Return an iterator to traverse all commits reachable as configured by the [Platform]. + /// + /// # Performance + /// + /// It's highly recommended to set an [`object cache`][Repository::object_cache_size()] on the parent repo + /// to greatly speed up performance if the returned id is supposed to be looked up right after. + pub fn all(self) -> Result, Error> { + self.selected(|_| true) + } } pub(crate) mod iter { @@ -78,50 +123,13 @@ pub(crate) mod iter { pub(crate) repo: &'repo crate::Repository, pub(crate) inner: Box> + 'repo>, - pub(crate) error_on_missing_commit: bool, - // TODO: tests - /// After iteration this flag is true if the iteration was stopped prematurely due to missing parent commits. - /// Note that this flag won't be `Some` if any iteration error occurs, which is the case if - /// [`error_on_missing_commit()`][Walk::error_on_missing_commit()] was called. - /// - /// This happens if a repository is a shallow clone. - /// Note that this value is `None` as long as the iteration isn't complete. - pub is_shallow: Option, - } - - impl<'repo> Walk<'repo> { - // TODO: tests - /// Once invoked, the iteration will return an error if a commit cannot be found in the object database. This typically happens - /// when operating on a shallow clone and thus is non-critical by default. - /// - /// Check the [`is_shallow`][Walk::is_shallow] field once the iteration ended otherwise to learn if a shallow commit graph - /// was encountered. - pub fn error_on_missing_commit(mut self) -> Self { - self.error_on_missing_commit = true; - self - } } impl<'repo> Iterator for Walk<'repo> { type Item = Result, gix_traverse::commit::ancestors::Error>; fn next(&mut self) -> Option { - match self.inner.next() { - None => { - self.is_shallow = Some(false); - None - } - Some(Ok(oid)) => Some(Ok(oid.attach(self.repo))), - Some(Err(err @ gix_traverse::commit::ancestors::Error::FindExisting { .. })) => { - if self.error_on_missing_commit { - Some(Err(err)) - } else { - self.is_shallow = Some(true); - None - } - } - Some(Err(err)) => Some(Err(err)), - } + self.inner.next().map(|res| res.map(|id| id.attach(self.repo))) } } } diff --git a/vendor/gix/src/shallow.rs b/vendor/gix/src/shallow.rs new file mode 100644 index 000000000..d49653a65 --- /dev/null +++ b/vendor/gix/src/shallow.rs @@ -0,0 +1,92 @@ +pub(crate) type CommitsStorage = + gix_features::threading::OwnShared>>; +/// A lazily loaded and auto-updated list of commits which are at the shallow boundary (behind which there are no commits available), +/// sorted to allow bisecting. +pub type Commits = gix_fs::SharedFileSnapshot>; + +/// +#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] +pub mod write { + pub(crate) mod function { + use std::io::Write; + + use gix_protocol::fetch::response::ShallowUpdate; + + use crate::shallow::{write::Error, Commits}; + + /// Write the previously obtained (possibly non-existing) `shallow_commits` to the shallow `file` + /// after applying all `updates`. + /// + /// If this leaves the list of shallow commits empty, the file is removed. + /// + /// ### Deviation + /// + /// Git also prunes the set of shallow commits while writing, we don't until we support some sort of pruning. + pub fn write( + mut file: gix_lock::File, + shallow_commits: Option, + updates: &[ShallowUpdate], + ) -> Result<(), Error> { + let mut shallow_commits = shallow_commits.map(|sc| (**sc).to_owned()).unwrap_or_default(); + for update in updates { + match update { + ShallowUpdate::Shallow(id) => { + shallow_commits.push(*id); + } + ShallowUpdate::Unshallow(id) => shallow_commits.retain(|oid| oid != id), + } + } + if shallow_commits.is_empty() { + std::fs::remove_file(file.resource_path())?; + drop(file); + return Ok(()); + } + + if shallow_commits.is_empty() { + if let Err(err) = std::fs::remove_file(file.resource_path()) { + if err.kind() != std::io::ErrorKind::NotFound { + return Err(err.into()); + } + } + } else { + shallow_commits.sort(); + let mut buf = Vec::::new(); + for commit in shallow_commits { + commit.write_hex_to(&mut buf).map_err(Error::Io)?; + buf.push(b'\n'); + } + file.write_all(&buf).map_err(Error::Io)?; + file.flush()?; + } + file.commit()?; + Ok(()) + } + } + + /// The error returned by [`write()`][crate::shallow::write()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Commit(#[from] gix_lock::commit::Error), + #[error("Could not remove an empty shallow file")] + RemoveEmpty(#[from] std::io::Error), + #[error("Failed to write object id to shallow file")] + Io(std::io::Error), + } +} +#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] +pub use write::function::write; + +/// +pub mod open { + /// The error returned by [`Repository::shallow_commits()`][crate::Repository::shallow_commits()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Could not open shallow file for reading")] + Io(#[from] std::io::Error), + #[error("Could not decode a line in shallow file as hex-encoded object hash")] + DecodeHash(#[from] gix_hash::decode::Error), + } +} diff --git a/vendor/gix/src/state.rs b/vendor/gix/src/state.rs new file mode 100644 index 000000000..d8ee99835 --- /dev/null +++ b/vendor/gix/src/state.rs @@ -0,0 +1,25 @@ +/// Tell what operation is currently in progress. +#[derive(Debug, PartialEq, Eq)] +pub enum InProgress { + /// A mailbox is being applied. + ApplyMailbox, + /// A rebase is happening while a mailbox is being applied. + // TODO: test + ApplyMailboxRebase, + /// A git bisect operation has not yet been concluded. + Bisect, + /// A cherry pick operation. + CherryPick, + /// A cherry pick with multiple commits pending. + CherryPickSequence, + /// A merge operation. + Merge, + /// A rebase operation. + Rebase, + /// An interactive rebase operation. + RebaseInteractive, + /// A revert operation. + Revert, + /// A revert operation with multiple commits pending. + RevertSequence, +} diff --git a/vendor/gix/src/types.rs b/vendor/gix/src/types.rs index 34ffdc8bf..eafa6a3f8 100644 --- a/vendor/gix/src/types.rs +++ b/vendor/gix/src/types.rs @@ -152,6 +152,7 @@ pub struct Repository { /// Particularly useful when following linked worktrees and instantiating new equally configured worktree repositories. pub(crate) options: crate::open::Options, pub(crate) index: crate::worktree::IndexStorage, + pub(crate) shallow_commits: crate::shallow::CommitsStorage, } /// An instance with access to everything a git repository entails, best imagined as container implementing `Sync + Send` for _most_ @@ -175,6 +176,7 @@ pub struct ThreadSafeRepository { pub(crate) linked_worktree_options: crate::open::Options, /// The index of this instances worktree. pub(crate) index: crate::worktree::IndexStorage, + pub(crate) shallow_commits: crate::shallow::CommitsStorage, } /// A remote which represents a way to interact with hosts for remote clones of the parent repository. diff --git a/vendor/gix/src/worktree/mod.rs b/vendor/gix/src/worktree/mod.rs index 19a44a900..8db123554 100644 --- a/vendor/gix/src/worktree/mod.rs +++ b/vendor/gix/src/worktree/mod.rs @@ -7,9 +7,9 @@ use crate::{ Repository, }; -pub(crate) type IndexStorage = gix_features::threading::OwnShared>; +pub(crate) type IndexStorage = gix_features::threading::OwnShared>; /// A lazily loaded and auto-updated worktree index. -pub type Index = gix_features::fs::SharedSnapshot; +pub type Index = gix_fs::SharedFileSnapshot; /// A stand-in to a worktree as result of a worktree iteration. /// @@ -71,18 +71,12 @@ pub mod proxy; /// pub mod open_index { - use crate::bstr::BString; - /// The error returned by [`Worktree::open_index()`][crate::Worktree::open_index()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { - #[error("Could not interpret value '{}' as 'index.threads'", .value)] - ConfigIndexThreads { - value: BString, - #[source] - err: gix_config::value::Error, - }, + #[error(transparent)] + ConfigIndexThreads(#[from] crate::config::key::GenericErrorWithValue), #[error(transparent)] IndexFile(#[from] gix_index::file::init::Error), } @@ -102,59 +96,64 @@ pub mod open_index { /// pub mod excludes { - use std::path::PathBuf; - /// The error returned by [`Worktree::excludes()`][crate::Worktree::excludes()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { - #[error("Could not read repository exclude.")] - Io(#[from] std::io::Error), #[error(transparent)] - EnvironmentPermission(#[from] gix_sec::permission::Error), - #[error("The value for `core.excludesFile` could not be read from configuration")] - ExcludesFilePathInterpolation(#[from] gix_config::path::interpolate::Error), + OpenIndex(#[from] crate::worktree::open_index::Error), + #[error(transparent)] + CreateCache(#[from] crate::config::exclude_stack::Error), } impl<'repo> crate::Worktree<'repo> { /// Configure a file-system cache checking if files below the repository are excluded. /// - /// This takes into consideration all the usual repository configuration. - // TODO: test, provide higher-level interface that is much easier to use and doesn't panic. - pub fn excludes( - &self, - index: &gix_index::State, - overrides: Option>, - ) -> Result { - let repo = self.parent; - let case = repo - .config - .ignore_case - .then_some(gix_glob::pattern::Case::Fold) - .unwrap_or_default(); - let mut buf = Vec::with_capacity(512); - let excludes_file = match repo.config.excludes_file().transpose()? { - Some(user_path) => Some(user_path), - None => repo.config.xdg_config_path("ignore")?, - }; - let state = gix_worktree::fs::cache::State::IgnoreStack(gix_worktree::fs::cache::state::Ignore::new( - overrides.unwrap_or_default(), - gix_attributes::MatchGroup::::from_git_dir( - repo.git_dir(), - excludes_file, - &mut buf, - )?, - None, - case, - )); - let attribute_list = state.build_attribute_list(index, index.path_backing(), case); - Ok(gix_worktree::fs::Cache::new( - self.path, - state, - case, - buf, - attribute_list, - )) + /// This takes into consideration all the usual repository configuration, namely: + /// + /// * `$XDG_CONFIG_HOME/…/ignore` if `core.excludesFile` is *not* set, otherwise use the configured file. + /// * `$GIT_DIR/info/exclude` if present. + /// + /// When only excludes are desired, this is the most efficient way to obtain them. Otherwise use + /// [`Worktree::attributes()`][crate::Worktree::attributes()] for accessing both attributes and excludes. + pub fn excludes(&self, overrides: Option) -> Result { + let index = self.index()?; + Ok(self.parent.excludes( + &index, + overrides, + gix_worktree::cache::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped, + )?) + } + } +} + +/// +pub mod attributes { + /// The error returned by [`Worktree::attributes()`][crate::Worktree::attributes()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + OpenIndex(#[from] crate::worktree::open_index::Error), + #[error(transparent)] + CreateCache(#[from] crate::attributes::Error), + } + + impl<'repo> crate::Worktree<'repo> { + /// Configure a file-system cache checking if files below the repository are excluded or for querying their attributes. + /// + /// This takes into consideration all the usual repository configuration, namely: + /// + /// * `$XDG_CONFIG_HOME/…/ignore|attributes` if `core.excludesFile|attributesFile` is *not* set, otherwise use the configured file. + /// * `$GIT_DIR/info/exclude|attributes` if present. + pub fn attributes(&self, overrides: Option) -> Result { + let index = self.index()?; + Ok(self.parent.attributes( + &index, + gix_worktree::cache::state::attributes::Source::WorktreeThenIdMapping, + gix_worktree::cache::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped, + overrides, + )?) } } } -- cgit v1.2.3