From 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:41:41 +0200 Subject: Merging upstream version 1.70.0+dfsg2. Signed-off-by: Daniel Baumann --- vendor/gix/src/config/cache/access.rs | 233 ++++++++ vendor/gix/src/config/cache/incubate.rs | 111 ++++ vendor/gix/src/config/cache/init.rs | 485 ++++++++++++++++ vendor/gix/src/config/cache/mod.rs | 18 + vendor/gix/src/config/cache/util.rs | 143 +++++ vendor/gix/src/config/mod.rs | 454 +++++++++++++++ vendor/gix/src/config/overrides.rs | 49 ++ vendor/gix/src/config/snapshot/_impls.rs | 76 +++ vendor/gix/src/config/snapshot/access.rs | 143 +++++ .../gix/src/config/snapshot/credential_helpers.rs | 183 ++++++ vendor/gix/src/config/snapshot/mod.rs | 5 + vendor/gix/src/config/tree/keys.rs | 629 +++++++++++++++++++++ vendor/gix/src/config/tree/mod.rs | 123 ++++ vendor/gix/src/config/tree/sections/author.rs | 23 + vendor/gix/src/config/tree/sections/branch.rs | 65 +++ vendor/gix/src/config/tree/sections/checkout.rs | 58 ++ vendor/gix/src/config/tree/sections/clone.rs | 20 + vendor/gix/src/config/tree/sections/committer.rs | 23 + vendor/gix/src/config/tree/sections/core.rs | 302 ++++++++++ vendor/gix/src/config/tree/sections/credential.rs | 56 ++ vendor/gix/src/config/tree/sections/diff.rs | 133 +++++ vendor/gix/src/config/tree/sections/extensions.rs | 59 ++ vendor/gix/src/config/tree/sections/gitoxide.rs | 363 ++++++++++++ vendor/gix/src/config/tree/sections/http.rs | 317 +++++++++++ vendor/gix/src/config/tree/sections/init.rs | 20 + vendor/gix/src/config/tree/sections/mod.rs | 96 ++++ vendor/gix/src/config/tree/sections/pack.rs | 64 +++ vendor/gix/src/config/tree/sections/protocol.rs | 85 +++ vendor/gix/src/config/tree/sections/remote.rs | 101 ++++ vendor/gix/src/config/tree/sections/safe.rs | 27 + vendor/gix/src/config/tree/sections/ssh.rs | 65 +++ vendor/gix/src/config/tree/sections/url.rs | 25 + vendor/gix/src/config/tree/sections/user.rs | 22 + vendor/gix/src/config/tree/traits.rs | 199 +++++++ 34 files changed, 4775 insertions(+) create mode 100644 vendor/gix/src/config/cache/access.rs create mode 100644 vendor/gix/src/config/cache/incubate.rs create mode 100644 vendor/gix/src/config/cache/init.rs create mode 100644 vendor/gix/src/config/cache/mod.rs create mode 100644 vendor/gix/src/config/cache/util.rs create mode 100644 vendor/gix/src/config/mod.rs create mode 100644 vendor/gix/src/config/overrides.rs create mode 100644 vendor/gix/src/config/snapshot/_impls.rs create mode 100644 vendor/gix/src/config/snapshot/access.rs create mode 100644 vendor/gix/src/config/snapshot/credential_helpers.rs create mode 100644 vendor/gix/src/config/snapshot/mod.rs create mode 100644 vendor/gix/src/config/tree/keys.rs create mode 100644 vendor/gix/src/config/tree/mod.rs create mode 100644 vendor/gix/src/config/tree/sections/author.rs create mode 100644 vendor/gix/src/config/tree/sections/branch.rs create mode 100644 vendor/gix/src/config/tree/sections/checkout.rs create mode 100644 vendor/gix/src/config/tree/sections/clone.rs create mode 100644 vendor/gix/src/config/tree/sections/committer.rs create mode 100644 vendor/gix/src/config/tree/sections/core.rs create mode 100644 vendor/gix/src/config/tree/sections/credential.rs create mode 100644 vendor/gix/src/config/tree/sections/diff.rs create mode 100644 vendor/gix/src/config/tree/sections/extensions.rs create mode 100644 vendor/gix/src/config/tree/sections/gitoxide.rs create mode 100644 vendor/gix/src/config/tree/sections/http.rs create mode 100644 vendor/gix/src/config/tree/sections/init.rs create mode 100644 vendor/gix/src/config/tree/sections/mod.rs create mode 100644 vendor/gix/src/config/tree/sections/pack.rs create mode 100644 vendor/gix/src/config/tree/sections/protocol.rs create mode 100644 vendor/gix/src/config/tree/sections/remote.rs create mode 100644 vendor/gix/src/config/tree/sections/safe.rs create mode 100644 vendor/gix/src/config/tree/sections/ssh.rs create mode 100644 vendor/gix/src/config/tree/sections/url.rs create mode 100644 vendor/gix/src/config/tree/sections/user.rs create mode 100644 vendor/gix/src/config/tree/traits.rs (limited to 'vendor/gix/src/config') diff --git a/vendor/gix/src/config/cache/access.rs b/vendor/gix/src/config/cache/access.rs new file mode 100644 index 000000000..8244eaf27 --- /dev/null +++ b/vendor/gix/src/config/cache/access.rs @@ -0,0 +1,233 @@ +#![allow(clippy::result_large_err)] +use std::{borrow::Cow, path::PathBuf, time::Duration}; + +use gix_lock::acquire::Fail; + +use crate::{ + bstr::BStr, + config, + config::{ + cache::util::{ApplyLeniency, ApplyLeniencyDefault}, + checkout_options, + tree::{Checkout, Core, Key}, + Cache, + }, + remote, + repository::identity, +}; + +/// Access +impl Cache { + pub(crate) fn diff_algorithm(&self) -> Result { + use crate::config::diff::algorithm::Error; + self.diff_algorithm + .get_or_try_init(|| { + let name = self + .resolved + .string("diff", None, "algorithm") + .unwrap_or_else(|| Cow::Borrowed("myers".into())); + config::tree::Diff::ALGORITHM + .try_into_algorithm(name) + .or_else(|err| match err { + Error::Unimplemented { .. } if self.lenient_config => Ok(gix_diff::blob::Algorithm::Histogram), + err => Err(err), + }) + .with_lenient_default(self.lenient_config) + }) + .copied() + } + + /// Returns a user agent for use with servers. + #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))] + pub(crate) fn user_agent_tuple(&self) -> (&'static str, Option>) { + use config::tree::Gitoxide; + let agent = self + .user_agent + .get_or_init(|| { + self.resolved + .string_by_key(Gitoxide::USER_AGENT.logical_name().as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| crate::env::agent().into()) + }) + .to_owned(); + ("agent", Some(gix_protocol::agent(agent).into())) + } + + pub(crate) fn personas(&self) -> &identity::Personas { + self.personas + .get_or_init(|| identity::Personas::from_config_and_env(&self.resolved)) + } + + pub(crate) fn url_rewrite(&self) -> &remote::url::Rewrite { + self.url_rewrite + .get_or_init(|| remote::url::Rewrite::from_config(&self.resolved, self.filter_config_section)) + } + + #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] + pub(crate) fn url_scheme(&self) -> Result<&remote::url::SchemePermission, config::protocol::allow::Error> { + self.url_scheme + .get_or_try_init(|| remote::url::SchemePermission::from_config(&self.resolved, self.filter_config_section)) + } + + pub(crate) fn diff_renames( + &self, + ) -> Result, crate::object::tree::diff::rewrites::Error> { + self.diff_renames + .get_or_try_init(|| { + crate::object::tree::diff::Rewrites::try_from_config(&self.resolved, self.lenient_config) + }) + .copied() + } + + /// Returns (file-timeout, pack-refs timeout) + pub(crate) fn lock_timeout( + &self, + ) -> Result<(gix_lock::acquire::Fail, gix_lock::acquire::Fail), config::lock_timeout::Error> { + let mut out: [gix_lock::acquire::Fail; 2] = Default::default(); + for (idx, (key, default_ms)) in [(&Core::FILES_REF_LOCK_TIMEOUT, 100), (&Core::PACKED_REFS_TIMEOUT, 1000)] + .into_iter() + .enumerate() + { + out[idx] = self + .resolved + .integer_filter("core", None, key.name, &mut self.filter_config_section.clone()) + .map(|res| key.try_into_lock_timeout(res)) + .transpose() + .with_leniency(self.lenient_config)? + .unwrap_or_else(|| Fail::AfterDurationWithBackoff(Duration::from_millis(default_ms))); + } + Ok((out[0], out[1])) + } + + /// The path to the user-level excludes file to ignore certain files in the worktree. + pub(crate) fn excludes_file(&self) -> Option> { + self.trusted_file_path("core", None, Core::EXCLUDES_FILE.name)? + .map(|p| p.into_owned()) + .into() + } + + /// A helper to obtain a file from trusted configuration at `section_name`, `subsection_name`, and `key`, which is interpolated + /// if present. + pub(crate) fn trusted_file_path( + &self, + section_name: impl AsRef, + subsection_name: Option<&BStr>, + key: impl AsRef, + ) -> Option, gix_config::path::interpolate::Error>> { + let path = self.resolved.path_filter( + section_name, + subsection_name, + key, + &mut self.filter_config_section.clone(), + )?; + + let install_dir = crate::path::install_dir().ok(); + let home = self.home_dir(); + let ctx = crate::config::cache::interpolate_context(install_dir.as_deref(), home.as_deref()); + Some(path.interpolate(ctx)) + } + + pub(crate) fn apply_leniency(&self, res: Option>) -> Result, E> { + res.transpose().with_leniency(self.lenient_config) + } + + /// Collect everything needed to checkout files into a worktree. + /// Note that some of the options being returned will be defaulted so safe settings, the caller might have to override them + /// depending on the use-case. + pub(crate) fn checkout_options( + &self, + git_dir: &std::path::Path, + ) -> Result { + fn boolean( + me: &Cache, + full_key: &str, + key: &'static config::tree::keys::Boolean, + default: bool, + ) -> Result { + debug_assert_eq!( + full_key, + key.logical_name(), + "BUG: key name and hardcoded name must match" + ); + Ok(me + .apply_leniency(me.resolved.boolean_by_key(full_key).map(|v| key.enrich_error(v)))? + .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)?, + }, + 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)?, + }) + } + 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)) + .or_else(|| { + std::env::var_os("HOME").map(|path| { + ( + { + let mut p = PathBuf::from(path); + p.push(".config"); + p + }, + &self.home_env, + ) + }) + }) + .and_then(|(base, permission)| { + let resource = base.join("git").join(resource_file_name); + permission.check(resource).transpose() + }) + .transpose() + } + + /// Return the home directory if we are allowed to read it and if it is set in the environment. + /// + /// 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)) + } +} diff --git a/vendor/gix/src/config/cache/incubate.rs b/vendor/gix/src/config/cache/incubate.rs new file mode 100644 index 000000000..047f2132b --- /dev/null +++ b/vendor/gix/src/config/cache/incubate.rs @@ -0,0 +1,111 @@ +#![allow(clippy::result_large_err)] +use super::{util, Error}; +use crate::config::tree::{Core, Extensions}; + +/// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the +/// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. +pub(crate) struct StageOne { + pub git_dir_config: gix_config::File<'static>, + pub buf: Vec, + + pub is_bare: bool, + pub lossy: Option, + pub object_hash: gix_hash::Kind, + pub reflog: Option, +} + +/// Initialization +impl StageOne { + pub fn new( + common_dir: &std::path::Path, + git_dir: &std::path::Path, + git_dir_trust: gix_sec::Trust, + lossy: Option, + lenient: bool, + ) -> Result { + let mut buf = Vec::with_capacity(512); + let mut config = load_config( + common_dir.join("config"), + &mut buf, + gix_config::Source::Local, + git_dir_trust, + lossy, + )?; + + // Note that we assume the repo is bare by default unless we are told otherwise. This is relevant if + // the repo doesn't have a configuration file. + let is_bare = util::config_bool(&config, &Core::BARE, "core.bare", true, lenient)?; + let repo_format_version = config + .integer_by_key("core.repositoryFormatVersion") + .map(|version| Core::REPOSITORY_FORMAT_VERSION.try_into_usize(version)) + .transpose()? + .unwrap_or_default(); + let object_hash = (repo_format_version != 1) + .then_some(Ok(gix_hash::Kind::Sha1)) + .or_else(|| { + config + .string("extensions", None, "objectFormat") + .map(|format| Extensions::OBJECT_FORMAT.try_into_object_format(format)) + }) + .transpose()? + .unwrap_or(gix_hash::Kind::Sha1); + + let extension_worktree = util::config_bool( + &config, + &Extensions::WORKTREE_CONFIG, + "extensions.worktreeConfig", + false, + lenient, + )?; + if extension_worktree { + let worktree_config = load_config( + git_dir.join("config.worktree"), + &mut buf, + gix_config::Source::Worktree, + git_dir_trust, + lossy, + )?; + config.append(worktree_config); + }; + + let reflog = util::query_refupdates(&config, lenient)?; + Ok(StageOne { + git_dir_config: config, + buf, + is_bare, + lossy, + object_hash, + reflog, + }) + } +} + +fn load_config( + config_path: std::path::PathBuf, + buf: &mut Vec, + source: gix_config::Source, + git_dir_trust: gix_sec::Trust, + lossy: Option, +) -> 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()), + }; + 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) + }, + )?; + + Ok(config) +} diff --git a/vendor/gix/src/config/cache/init.rs b/vendor/gix/src/config/cache/init.rs new file mode 100644 index 000000000..dc76f78bb --- /dev/null +++ b/vendor/gix/src/config/cache/init.rs @@ -0,0 +1,485 @@ +#![allow(clippy::result_large_err)] +use std::borrow::Cow; + +use gix_sec::Permission; + +use super::{interpolate_context, util, Error, StageOne}; +use crate::{ + bstr::BString, + config, + config::{ + cache::util::ApplyLeniency, + tree::{gitoxide, Core, Http}, + Cache, + }, + repository, +}; + +/// Initialization +impl Cache { + #[allow(clippy::too_many_arguments)] + pub fn from_stage_one( + StageOne { + git_dir_config, + mut buf, + lossy, + is_bare, + object_hash, + reflog: _, + }: StageOne, + git_dir: &std::path::Path, + branch_name: Option<&gix_ref::FullNameRef>, + filter_config_section: fn(&gix_config::file::Metadata) -> bool, + git_install_dir: Option<&std::path::Path>, + home: Option<&std::path::Path>, + repository::permissions::Environment { + git_prefix, + home: home_env, + xdg_config_home: xdg_config_home_env, + ssh_prefix: _, + http_transport, + identity, + objects, + }: repository::permissions::Environment, + repository::permissions::Config { + git_binary: use_installation, + system: use_system, + git: use_git, + user: use_user, + env: use_env, + includes: use_includes, + }: repository::permissions::Config, + lenient_config: bool, + api_config_overrides: &[BString], + cli_config_overrides: &[BString], + ) -> Result { + let options = gix_config::file::init::Options { + includes: if use_includes { + gix_config::file::includes::Options::follow( + interpolate_context(git_install_dir, home), + gix_config::file::includes::conditional::Context { + git_dir: git_dir.into(), + branch_name, + }, + ) + } else { + gix_config::file::includes::Options::no_follow() + }, + ..util::base_options(lossy) + }; + + 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, + gix_config::source::Kind::System, + gix_config::source::Kind::Global, + ] + .iter() + .flat_map(|kind| kind.sources()) + .filter_map(|source| { + match source { + gix_config::Source::GitInstallation if !use_installation => return None, + gix_config::Source::System if !use_system => return None, + gix_config::Source::Git if !use_git => return None, + gix_config::Source::User if !use_user => return None, + _ => {} + } + 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)) + }) + .map(|p| (source, p.into_owned())) + }) + .map(|(source, path)| gix_config::file::Metadata { + path: Some(path), + source: *source, + level: 0, + trust: gix_sec::Trust::Full, + }); + + let err_on_nonexisting_paths = false; + let mut globals = gix_config::File::from_paths_metadata_buf( + metas, + &mut buf, + err_on_nonexisting_paths, + gix_config::file::init::Options { + includes: gix_config::file::includes::Options::no_follow(), + ..options + }, + ) + .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(), + })? + .unwrap_or_default(); + + let local_meta = git_dir_config.meta_owned(); + globals.append(git_dir_config); + globals.resolve_includes(options)?; + if use_env { + globals.append(gix_config::File::from_env(options)?.unwrap_or_default()); + } + if !cli_config_overrides.is_empty() { + config::overrides::append(&mut globals, cli_config_overrides, gix_config::Source::Cli, |_| None) + .map_err(|err| Error::ConfigOverrides { + err, + source: gix_config::Source::Cli, + })?; + } + if !api_config_overrides.is_empty() { + config::overrides::append(&mut globals, api_config_overrides, gix_config::Source::Api, |_| None) + .map_err(|err| Error::ConfigOverrides { + err, + source: gix_config::Source::Api, + })?; + } + apply_environment_overrides(&mut globals, *git_prefix, http_transport, identity, objects)?; + globals.set_meta(local_meta); + globals + }; + + let hex_len = util::parse_core_abbrev(&config, object_hash).with_leniency(lenient_config)?; + + use util::config_bool; + let reflog = util::query_refupdates(&config, lenient_config)?; + let ignore_case = config_bool(&config, &Core::IGNORE_CASE, "core.ignoreCase", false, lenient_config)?; + let use_multi_pack_index = config_bool( + &config, + &Core::MULTIPACK_INDEX, + "core.multiPackIndex", + true, + lenient_config, + )?; + let object_kind_hint = util::disambiguate_hint(&config, lenient_config)?; + let (pack_cache_bytes, object_cache_bytes) = + util::parse_object_caches(&config, lenient_config, filter_config_section)?; + // NOTE: When adding a new initial cache, consider adjusting `reread_values_and_clear_caches()` as well. + Ok(Cache { + resolved: config.into(), + use_multi_pack_index, + object_hash, + object_kind_hint, + pack_cache_bytes, + object_cache_bytes, + reflog, + is_bare, + ignore_case, + hex_len, + filter_config_section, + xdg_config_home_env, + home_env, + lenient_config, + user_agent: Default::default(), + personas: Default::default(), + url_rewrite: Default::default(), + diff_renames: Default::default(), + #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] + url_scheme: Default::default(), + diff_algorithm: Default::default(), + }) + } + + /// Call this with new `config` to update values and clear caches. Note that none of the values will be applied if a single + /// one is invalid. + /// However, those that are lazily read won't be re-evaluated right away and might thus pass now but fail later. + /// + /// Note that we unconditionally re-read all values. + pub fn reread_values_and_clear_caches_replacing_config(&mut self, config: crate::Config) -> Result<(), Error> { + let prev = std::mem::replace(&mut self.resolved, config); + match self.reread_values_and_clear_caches() { + Err(err) => { + drop(std::mem::replace(&mut self.resolved, prev)); + Err(err) + } + Ok(()) => Ok(()), + } + } + + /// Similar to `reread_values_and_clear_caches_replacing_config()`, but works on the existing configuration instead of a passed + /// in one that it them makes the default. + pub fn reread_values_and_clear_caches(&mut self) -> Result<(), Error> { + let config = &self.resolved; + let hex_len = util::parse_core_abbrev(config, self.object_hash).with_leniency(self.lenient_config)?; + + use util::config_bool; + let ignore_case = config_bool( + config, + &Core::IGNORE_CASE, + "core.ignoreCase", + false, + self.lenient_config, + )?; + let object_kind_hint = util::disambiguate_hint(config, self.lenient_config)?; + let reflog = util::query_refupdates(config, self.lenient_config)?; + + self.hex_len = hex_len; + self.ignore_case = ignore_case; + self.object_kind_hint = object_kind_hint; + self.reflog = reflog; + + self.user_agent = Default::default(); + self.personas = Default::default(); + self.url_rewrite = Default::default(); + self.diff_renames = Default::default(); + self.diff_algorithm = Default::default(); + (self.pack_cache_bytes, self.object_cache_bytes) = + util::parse_object_caches(config, self.lenient_config, self.filter_config_section)?; + #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] + { + self.url_scheme = Default::default(); + } + + Ok(()) + } +} + +impl crate::Repository { + /// Replace our own configuration with `config` and re-read all cached values, and apply them to select in-memory instances. + pub(crate) fn reread_values_and_clear_caches_replacing_config( + &mut self, + config: crate::Config, + ) -> Result<(), Error> { + self.config.reread_values_and_clear_caches_replacing_config(config)?; + self.apply_changed_values(); + Ok(()) + } + + fn apply_changed_values(&mut self) { + self.refs.write_reflog = util::reflog_or_default(self.config.reflog, self.work_dir().is_some()); + } +} + +fn apply_environment_overrides( + config: &mut gix_config::File<'static>, + git_prefix: Permission, + http_transport: Permission, + identity: Permission, + objects: Permission, +) -> Result<(), Error> { + fn env(key: &'static dyn config::tree::Key) -> &'static str { + key.the_environment_override() + } + fn var_as_bstring(var: &str, perm: Permission) -> Option { + perm.check_opt(var) + .and_then(std::env::var_os) + .and_then(|val| gix_path::os_string_into_bstring(val).ok()) + } + + let mut env_override = gix_config::File::new(gix_config::file::Metadata::from(gix_config::Source::EnvOverride)); + for (section_name, subsection_name, permission, data) in [ + ( + "http", + None, + http_transport, + &[ + ("GIT_HTTP_LOW_SPEED_LIMIT", "lowSpeedLimit"), + ("GIT_HTTP_LOW_SPEED_TIME", "lowSpeedTime"), + ("GIT_HTTP_USER_AGENT", "userAgent"), + { + let key = &Http::SSL_CA_INFO; + (env(key), key.name) + }, + { + let key = &Http::SSL_VERSION; + (env(key), key.name) + }, + ][..], + ), + ( + "gitoxide", + Some(Cow::Borrowed("https".into())), + http_transport, + &[ + ("HTTPS_PROXY", gitoxide::Https::PROXY.name), + ("https_proxy", gitoxide::Https::PROXY.name), + ], + ), + ( + "gitoxide", + Some(Cow::Borrowed("http".into())), + http_transport, + &[ + ("ALL_PROXY", "allProxy"), + { + let key = &gitoxide::Http::ALL_PROXY; + (env(key), key.name) + }, + ("NO_PROXY", "noProxy"), + { + let key = &gitoxide::Http::NO_PROXY; + (env(key), key.name) + }, + { + let key = &gitoxide::Http::PROXY; + (env(key), key.name) + }, + { + let key = &gitoxide::Http::VERBOSE; + (env(key), key.name) + }, + { + let key = &gitoxide::Http::PROXY_AUTH_METHOD; + (env(key), key.name) + }, + ], + ), + ( + "gitoxide", + Some(Cow::Borrowed("committer".into())), + identity, + &[ + { + let key = &gitoxide::Committer::NAME_FALLBACK; + (env(key), key.name) + }, + { + let key = &gitoxide::Committer::EMAIL_FALLBACK; + (env(key), key.name) + }, + ], + ), + ( + "gitoxide", + Some(Cow::Borrowed("author".into())), + identity, + &[ + { + let key = &gitoxide::Author::NAME_FALLBACK; + (env(key), key.name) + }, + { + let key = &gitoxide::Author::EMAIL_FALLBACK; + (env(key), key.name) + }, + ], + ), + ( + "gitoxide", + Some(Cow::Borrowed("commit".into())), + git_prefix, + &[ + { + let key = &gitoxide::Commit::COMMITTER_DATE; + (env(key), key.name) + }, + { + let key = &gitoxide::Commit::AUTHOR_DATE; + (env(key), key.name) + }, + ], + ), + ( + "gitoxide", + Some(Cow::Borrowed("allow".into())), + http_transport, + &[("GIT_PROTOCOL_FROM_USER", "protocolFromUser")], + ), + ( + "gitoxide", + Some(Cow::Borrowed("user".into())), + identity, + &[{ + let key = &gitoxide::User::EMAIL_FALLBACK; + (env(key), key.name) + }], + ), + ( + "gitoxide", + Some(Cow::Borrowed("objects".into())), + objects, + &[ + { + let key = &gitoxide::Objects::NO_REPLACE; + (env(key), key.name) + }, + { + let key = &gitoxide::Objects::REPLACE_REF_BASE; + (env(key), key.name) + }, + { + let key = &gitoxide::Objects::CACHE_LIMIT; + (env(key), key.name) + }, + ], + ), + ( + "gitoxide", + Some(Cow::Borrowed("ssh".into())), + git_prefix, + &[{ + let key = &gitoxide::Ssh::COMMAND_WITHOUT_SHELL_FALLBACK; + (env(key), key.name) + }], + ), + ( + "ssh", + None, + git_prefix, + &[{ + let key = &config::tree::Ssh::VARIANT; + (env(key), key.name) + }], + ), + ] { + let mut section = env_override + .new_section(section_name, subsection_name) + .expect("statically known valid section name"); + for (var, key) in data { + if let Some(value) = var_as_bstring(var, permission) { + section.push_with_comment( + (*key).try_into().expect("statically known to be valid"), + Some(value.as_ref()), + format!("from {var}").as_str(), + ); + } + } + if section.num_values() == 0 { + let id = section.id(); + env_override.remove_section_by_id(id); + } + } + + { + let mut section = env_override + .new_section("core", None) + .expect("statically known valid section name"); + + for (var, key, permission) in [ + { + let key = &Core::DELTA_BASE_CACHE_LIMIT; + (env(key), key.name, objects) + }, + { + let key = &Core::SSH_COMMAND; + (env(key), key.name, git_prefix) + }, + ] { + if let Some(value) = var_as_bstring(var, permission) { + section.push_with_comment( + key.try_into().expect("statically known to be valid"), + Some(value.as_ref()), + format!("from {var}").as_str(), + ); + } + } + + if section.num_values() == 0 { + let id = section.id(); + env_override.remove_section_by_id(id); + } + } + + if !env_override.is_void() { + config.append(env_override); + } + Ok(()) +} diff --git a/vendor/gix/src/config/cache/mod.rs b/vendor/gix/src/config/cache/mod.rs new file mode 100644 index 000000000..1904c5ea9 --- /dev/null +++ b/vendor/gix/src/config/cache/mod.rs @@ -0,0 +1,18 @@ +use super::{Cache, Error}; + +mod incubate; +pub(crate) use incubate::StageOne; + +mod init; + +impl std::fmt::Debug for Cache { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Cache").finish_non_exhaustive() + } +} + +mod access; + +pub(crate) mod util; + +pub(crate) use util::interpolate_context; diff --git a/vendor/gix/src/config/cache/util.rs b/vendor/gix/src/config/cache/util.rs new file mode 100644 index 000000000..c12f850e6 --- /dev/null +++ b/vendor/gix/src/config/cache/util.rs @@ -0,0 +1,143 @@ +#![allow(clippy::result_large_err)] +use super::Error; +use crate::{ + config, + config::tree::{gitoxide, Core}, + revision::spec::parse::ObjectKindHint, +}; + +pub(crate) fn interpolate_context<'a>( + git_install_dir: Option<&'a std::path::Path>, + home_dir: Option<&'a std::path::Path>, +) -> gix_config::path::interpolate::Context<'a> { + gix_config::path::interpolate::Context { + git_install_dir, + home_dir, + home_for_user: Some(gix_config::path::interpolate::home_for_user), // TODO: figure out how to configure this + } +} + +pub(crate) fn base_options(lossy: Option) -> gix_config::file::init::Options<'static> { + gix_config::file::init::Options { + lossy: lossy.unwrap_or(!cfg!(debug_assertions)), + ..Default::default() + } +} + +pub(crate) fn config_bool( + config: &gix_config::File<'_>, + key: &'static config::tree::keys::Boolean, + key_str: &str, + default: bool, + lenient: bool, +) -> Result { + use config::tree::Key; + debug_assert_eq!( + key_str, + key.logical_name(), + "BUG: key name and hardcoded name must match" + ); + config + .boolean_by_key(key_str) + .map(|res| key.enrich_error(res)) + .unwrap_or(Ok(default)) + .map_err(Error::from) + .with_lenient_default(lenient) +} + +pub(crate) fn query_refupdates( + config: &gix_config::File<'static>, + lenient_config: bool, +) -> Result, Error> { + let key = "core.logAllRefUpdates"; + Core::LOG_ALL_REF_UPDATES + .try_into_ref_updates(config.boolean_by_key(key), || config.string_by_key(key)) + .with_leniency(lenient_config) + .map_err(Into::into) +} + +pub(crate) fn reflog_or_default( + config_reflog: Option, + has_worktree: bool, +) -> gix_ref::store::WriteReflog { + config_reflog.unwrap_or(if has_worktree { + gix_ref::store::WriteReflog::Normal + } else { + gix_ref::store::WriteReflog::Disable + }) +} + +/// Return `(pack_cache_bytes, object_cache_bytes)` as parsed from gix-config +pub(crate) fn parse_object_caches( + config: &gix_config::File<'static>, + lenient: bool, + mut filter_config_section: fn(&gix_config::file::Metadata) -> bool, +) -> Result<(Option, usize), Error> { + let pack_cache_bytes = config + .integer_filter_by_key("core.deltaBaseCacheLimit", &mut filter_config_section) + .map(|res| Core::DELTA_BASE_CACHE_LIMIT.try_into_usize(res)) + .transpose() + .with_leniency(lenient)?; + let object_cache_bytes = config + .integer_filter_by_key("gitoxide.objects.cacheLimit", &mut filter_config_section) + .map(|res| gitoxide::Objects::CACHE_LIMIT.try_into_usize(res)) + .transpose() + .with_leniency(lenient)? + .unwrap_or_default(); + Ok((pack_cache_bytes, object_cache_bytes)) +} + +pub(crate) fn parse_core_abbrev( + config: &gix_config::File<'static>, + object_hash: gix_hash::Kind, +) -> Result, Error> { + Ok(config + .string_by_key("core.abbrev") + .map(|abbrev| Core::ABBREV.try_into_abbreviation(abbrev, object_hash)) + .transpose()? + .flatten()) +} + +pub(crate) fn disambiguate_hint( + config: &gix_config::File<'static>, + lenient_config: bool, +) -> Result, config::key::GenericErrorWithValue> { + match config.string_by_key("core.disambiguate") { + None => Ok(None), + Some(value) => Core::DISAMBIGUATE + .try_into_object_kind_hint(value) + .with_leniency(lenient_config), + } +} + +// TODO: Use a specialization here once trait specialization is stabilized. Would be perfect here for `T: Default`. +pub trait ApplyLeniency { + fn with_leniency(self, is_lenient: bool) -> Self; +} + +pub trait ApplyLeniencyDefault { + fn with_lenient_default(self, is_lenient: bool) -> Self; +} + +impl ApplyLeniency for Result, E> { + fn with_leniency(self, is_lenient: bool) -> Self { + match self { + Ok(v) => Ok(v), + Err(_) if is_lenient => Ok(None), + Err(err) => Err(err), + } + } +} + +impl ApplyLeniencyDefault for Result +where + T: Default, +{ + fn with_lenient_default(self, is_lenient: bool) -> Self { + match self { + Ok(v) => Ok(v), + Err(_) if is_lenient => Ok(T::default()), + Err(err) => Err(err), + } + } +} diff --git a/vendor/gix/src/config/mod.rs b/vendor/gix/src/config/mod.rs new file mode 100644 index 000000000..1e2566777 --- /dev/null +++ b/vendor/gix/src/config/mod.rs @@ -0,0 +1,454 @@ +pub use gix_config::*; +use gix_features::threading::OnceCell; + +use crate::{bstr::BString, repository::identity, revision::spec, Repository}; + +pub(crate) mod cache; +mod snapshot; +pub use snapshot::credential_helpers; + +/// +pub mod overrides; + +pub mod tree; +pub use tree::root::Tree; + +/// A platform to access configuration values as read from disk. +/// +/// Note that these values won't update even if the underlying file(s) change. +pub struct Snapshot<'repo> { + pub(crate) repo: &'repo Repository, +} + +/// A platform to access configuration values and modify them in memory, while making them available when this platform is dropped +/// as form of auto-commit. +/// Note that the values will only affect this instance of the parent repository, and not other clones that may exist. +/// +/// Note that these values won't update even if the underlying file(s) change. +/// +/// Use [`forget()`][Self::forget()] to not apply any of the changes. +// TODO: make it possible to load snapshots with reloading via .config() and write mutated snapshots back to disk which should be the way +// to affect all instances of a repo, probably via `config_mut()` and `config_mut_at()`. +pub struct SnapshotMut<'repo> { + pub(crate) repo: Option<&'repo mut Repository>, + pub(crate) config: gix_config::File<'static>, +} + +/// A utility structure created by [`SnapshotMut::commit_auto_rollback()`] that restores the previous configuration on drop. +pub struct CommitAutoRollback<'repo> { + pub(crate) repo: Option<&'repo mut Repository>, + pub(crate) prev_config: crate::Config, +} + +pub(crate) mod section { + pub fn is_trusted(meta: &gix_config::file::Metadata) -> bool { + meta.trust == gix_sec::Trust::Full || meta.source.kind() != gix_config::source::Kind::Repository + } +} + +/// The error returned when failing to initialize the repository configuration. +/// +/// This configuration is on the critical path when opening a repository. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + ConfigBoolean(#[from] boolean::Error), + #[error(transparent)] + ConfigUnsigned(#[from] unsigned_integer::Error), + #[error(transparent)] + ConfigTypedString(#[from] key::GenericErrorWithValue), + #[error("Cannot handle objects formatted as {:?}", .name)] + UnsupportedObjectFormat { name: BString }, + #[error(transparent)] + CoreAbbrev(#[from] abbrev::Error), + #[error("Could not read configuration file")] + Io(#[from] std::io::Error), + #[error(transparent)] + Init(#[from] gix_config::file::init::Error), + #[error(transparent)] + ResolveIncludes(#[from] gix_config::file::includes::Error), + #[error(transparent)] + FromEnv(#[from] gix_config::file::init::from_env::Error), + #[error(transparent)] + PathInterpolation(#[from] gix_config::path::interpolate::Error), + #[error("{source:?} configuration overrides at open or init time could not be applied.")] + ConfigOverrides { + #[source] + err: overrides::Error, + source: gix_config::Source, + }, +} + +/// +pub mod diff { + /// + pub mod algorithm { + use crate::bstr::BString; + + /// The error produced when obtaining `diff.algorithm`. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Unknown diff algorithm named '{name}'")] + Unknown { name: BString }, + #[error("The '{name}' algorithm is not yet implemented")] + Unimplemented { name: BString }, + } + } +} + +/// +pub mod checkout_options { + /// The error produced when collecting all information needed for checking out files into a worktree. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + ConfigCheckStat(#[from] super::key::GenericErrorWithValue), + #[error(transparent)] + ConfigBoolean(#[from] super::boolean::Error), + #[error(transparent)] + CheckoutWorkers(#[from] super::checkout::workers::Error), + #[error("Failed to interpolate the attribute file configured at `core.attributesFile`")] + AttributesFileInterpolation(#[from] gix_config::path::interpolate::Error), + } +} + +/// +pub mod protocol { + /// + pub mod allow { + use crate::bstr::BString; + + /// The error returned when obtaining the permission for a particular scheme. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + #[error("The value {value:?} must be allow|deny|user in configuration key protocol{0}.allow", scheme.as_ref().map(|s| format!(".{s}")).unwrap_or_default())] + pub struct Error { + pub scheme: Option, + pub value: BString, + } + } +} + +/// +pub mod ssh_connect_options { + /// The error produced when obtaining ssh connection configuration. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + #[error(transparent)] + pub struct Error(#[from] super::key::GenericErrorWithValue); +} + +/// +pub mod key { + use crate::bstr::BString; + + const fn prefix(kind: char) -> &'static str { + match kind { + 'n' => "", // nothing + 'k' => "The value of key", // generic key + 't' => "The date format at key", // time + 'i' => "The timeout at key", // timeout + 'd' => "The duration [ms] at key", // duration + 'b' => "The boolean at key", // boolean + 'v' => "The key", // generic key with value + 'r' => "The refspec at", // refspec + 's' => "The ssl version at", // ssl-version + 'u' => "The url at", // url + 'w' => "The utf-8 string at", // string + _ => panic!("BUG: invalid prefix kind - add a case for it here"), + } + } + const fn suffix(kind: char) -> &'static str { + match kind { + 'd' => "could not be decoded", // decoding + 'i' => "was invalid", // invalid + 'u' => "could not be parsed as unsigned integer", // unsigned integer + 'p' => "could not be parsed", // parsing + _ => panic!("BUG: invalid suffix kind - add a case for it here"), + } + } + /// A generic error suitable to produce decent messages for all kinds of configuration errors with config-key granularity. + /// + /// This error is meant to be reusable and help produce uniform error messages related to parsing any configuration key. + #[derive(Debug, thiserror::Error)] + #[error("{} \"{key}{}\"{} {}", prefix(PREFIX), value.as_ref().map(|v| format!("={v}")).unwrap_or_default(), environment_override.as_deref().map(|var| format!(" (possibly from {var})")).unwrap_or_default(), suffix(SUFFIX))] + pub struct Error { + /// The configuration key that contained the value. + pub key: BString, + /// The value that was assigned to `key`. + pub value: Option, + /// The associated environment variable that would override this value. + pub environment_override: Option<&'static str>, + /// The source of the error if there was one. + pub source: Option, + } + + /// Initialization + /// Instantiate a new error from the given `key`. + /// + /// Note that specifics of the error message are defined by the `PREFIX` and `SUFFIX` which is usually defined by a typedef. + impl From<&'static T> for Error + where + E: std::error::Error + Send + Sync + 'static, + T: super::tree::Key, + { + fn from(key: &'static T) -> Self { + Error { + key: key.logical_name().into(), + value: None, + environment_override: key.environment_override(), + source: None, + } + } + } + + /// Initialization + impl Error + where + E: std::error::Error + Send + Sync + 'static, + { + /// Instantiate an error with all data from `key` along with the `value` of the key. + pub fn from_value(key: &'static impl super::tree::Key, value: BString) -> Self { + Error::from(key).with_value(value) + } + } + + /// Builder + impl Error + where + E: std::error::Error + Send + Sync + 'static, + { + /// Attach the given `err` as source. + pub fn with_source(mut self, err: E) -> Self { + self.source = Some(err); + self + } + + /// Attach the given `value` as value we observed when the error was produced. + pub fn with_value(mut self, value: BString) -> Self { + self.value = Some(value); + self + } + } + + /// A generic key error for use when it doesn't seem worth it say more than 'key is invalid' along with meta-data. + pub type GenericError = Error; + + /// A generic key error which will also contain a value. + pub type GenericErrorWithValue = Error; +} + +/// +pub mod checkout { + /// + pub mod workers { + use crate::config; + + /// The error produced when failing to parse the the `checkout.workers` key. + pub type Error = config::key::Error; + } +} + +/// +pub mod abbrev { + use crate::bstr::BString; + + /// The error describing an incorrect `core.abbrev` value. + #[derive(Debug, thiserror::Error)] + #[error("Invalid value for 'core.abbrev' = '{}'. It must be between 4 and {}", .value, .max)] + pub struct Error { + /// The value found in the git configuration + pub value: BString, + /// The maximum abbreviation length, the length of an object hash. + pub max: u8, + } +} + +/// +pub mod remote { + /// + pub mod symbolic_name { + /// The error produced when failing to produce a symbolic remote name from configuration. + pub type Error = super::super::key::Error; + } +} + +/// +pub mod time { + /// The error produced when failing to parse time from configuration. + pub type Error = super::key::Error; +} + +/// +pub mod lock_timeout { + /// The error produced when failing to parse timeout for locks. + pub type Error = super::key::Error; +} + +/// +pub mod duration { + /// The error produced when failing to parse durations (in milliseconds). + pub type Error = super::key::Error; +} + +/// +pub mod boolean { + /// The error produced when failing to parse time from configuration. + pub type Error = super::key::Error; +} + +/// +pub mod unsigned_integer { + /// The error produced when failing to parse a signed integer from configuration. + pub type Error = super::key::Error; +} + +/// +pub mod url { + /// The error produced when failing to parse a url from the configuration. + pub type Error = super::key::Error; +} + +/// +pub mod string { + /// The error produced when failing to interpret configuration as UTF-8 encoded string. + pub type Error = super::key::Error; +} + +/// +pub mod refspec { + /// The error produced when failing to parse a refspec from the configuration. + pub type Error = super::key::Error; +} + +/// +pub mod ssl_version { + /// The error produced when failing to parse a refspec from the configuration. + pub type Error = super::key::Error; +} + +/// +pub mod transport { + use std::borrow::Cow; + + use crate::bstr::BStr; + + /// The error produced when configuring a transport for a particular protocol. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error( + "Could not interpret configuration key {key:?} as {kind} integer of desired range with value: {actual}" + )] + InvalidInteger { + key: &'static str, + kind: &'static str, + actual: i64, + }, + #[error("Could not interpret configuration key {key:?}")] + ConfigValue { + source: gix_config::value::Error, + key: &'static str, + }, + #[error("Could not interpolate path at key {key:?}")] + InterpolatePath { + source: gix_config::path::interpolate::Error, + key: &'static str, + }, + #[error("Could not decode value at key {key:?} as UTF-8 string")] + IllformedUtf8 { + key: Cow<'static, BStr>, + source: crate::config::string::Error, + }, + #[error("Invalid URL passed for configuration")] + ParseUrl(#[from] gix_url::parse::Error), + #[error("Could obtain configuration for an HTTP url")] + Http(#[from] http::Error), + } + + /// + pub mod http { + use std::borrow::Cow; + + use crate::bstr::BStr; + + /// The error produced when configuring a HTTP transport. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Boolean(#[from] crate::config::boolean::Error), + #[error(transparent)] + UnsignedInteger(#[from] crate::config::unsigned_integer::Error), + #[error(transparent)] + ConnectTimeout(#[from] crate::config::duration::Error), + #[error("The proxy authentication at key `{key}` is invalid")] + InvalidProxyAuthMethod { + source: crate::config::key::GenericErrorWithValue, + key: Cow<'static, BStr>, + }, + #[error("Could not configure the credential helpers for the authenticated proxy url")] + ConfigureProxyAuthenticate(#[from] crate::config::snapshot::credential_helpers::Error), + #[error(transparent)] + InvalidSslVersion(#[from] crate::config::ssl_version::Error), + #[error("The HTTP version must be 'HTTP/2' or 'HTTP/1.1'")] + InvalidHttpVersion(#[from] crate::config::key::GenericErrorWithValue), + #[error("The follow redirects value 'initial', or boolean true or false")] + InvalidFollowRedirects(#[source] crate::config::key::GenericErrorWithValue), + } + } +} + +/// Utility type to keep pre-obtained configuration values, only for those required during initial setup +/// and other basic operations that are common enough to warrant a permanent cache. +/// +/// All other values are obtained lazily using OnceCell. +#[derive(Clone)] +pub(crate) struct Cache { + pub resolved: crate::Config, + /// The hex-length to assume when shortening object ids. If `None`, it should be computed based on the approximate object count. + pub hex_len: Option, + /// true if the repository is designated as 'bare', without work tree. + pub is_bare: bool, + /// The type of hash to use. + pub object_hash: gix_hash::Kind, + /// If true, multi-pack indices, whether present or not, may be used by the object database. + pub use_multi_pack_index: bool, + /// The representation of `core.logallrefupdates`, or `None` if the variable wasn't set. + pub reflog: Option, + /// The configured user agent for presentation to servers. + pub(crate) user_agent: OnceCell, + /// identities for later use, lazy initialization. + pub(crate) personas: OnceCell, + /// A lazily loaded rewrite list for remote urls + pub(crate) url_rewrite: OnceCell, + /// The lazy-loaded rename information for diffs. + pub(crate) diff_renames: OnceCell>, + /// A lazily loaded mapping to know which url schemes to allow + #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] + pub(crate) url_scheme: OnceCell, + /// The algorithm to use when diffing blobs + pub(crate) diff_algorithm: OnceCell, + /// The amount of bytes to use for a memory backed delta pack cache. If `Some(0)`, no cache is used, if `None` + /// a standard cache is used which costs near to nothing and always pays for itself. + pub(crate) pack_cache_bytes: Option, + /// The amount of bytes to use for caching whole objects, or 0 to turn it off entirely. + pub(crate) object_cache_bytes: usize, + /// The config section filter from the options used to initialize this instance. Keep these in sync! + filter_config_section: fn(&gix_config::file::Metadata) -> bool, + /// The object kind to pick if a prefix is ambiguous. + pub object_kind_hint: Option, + /// If true, we are on a case-insensitive file system. + pub ignore_case: bool, + /// 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, + // TODO: make core.precomposeUnicode available as well. +} diff --git a/vendor/gix/src/config/overrides.rs b/vendor/gix/src/config/overrides.rs new file mode 100644 index 000000000..f43e8471b --- /dev/null +++ b/vendor/gix/src/config/overrides.rs @@ -0,0 +1,49 @@ +use std::convert::TryFrom; + +use crate::bstr::{BStr, BString, ByteSlice}; + +/// The error returned by [SnapshotMut::apply_cli_overrides()][crate::config::SnapshotMut::append_config()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("{input:?} is not a valid configuration key. Examples are 'core.abbrev' or 'remote.origin.url'")] + InvalidKey { input: BString }, + #[error("Key {key:?} could not be parsed")] + SectionKey { + key: BString, + source: gix_config::parse::section::key::Error, + }, + #[error(transparent)] + SectionHeader(#[from] gix_config::parse::section::header::Error), +} + +pub(crate) fn append( + config: &mut gix_config::File<'static>, + values: impl IntoIterator>, + source: gix_config::Source, + mut make_comment: impl FnMut(&BStr) -> Option, +) -> Result<(), Error> { + let mut file = gix_config::File::new(gix_config::file::Metadata::from(source)); + for key_value in values { + let key_value = key_value.as_ref(); + let mut tokens = key_value.splitn(2, |b| *b == b'=').map(|v| v.trim()); + let key = tokens.next().expect("always one value").as_bstr(); + let value = tokens.next(); + let key = gix_config::parse::key(key.to_str().map_err(|_| Error::InvalidKey { input: key.into() })?) + .ok_or_else(|| Error::InvalidKey { input: key.into() })?; + let mut section = file.section_mut_or_create_new(key.section_name, key.subsection_name)?; + let key = + gix_config::parse::section::Key::try_from(key.value_name.to_owned()).map_err(|err| Error::SectionKey { + source: err, + key: key.value_name.into(), + })?; + let comment = make_comment(key_value); + let value = value.map(|v| v.as_bstr()); + match comment { + Some(comment) => section.push_with_comment(key, value, &**comment), + None => section.push(key, value), + }; + } + config.append(file); + Ok(()) +} diff --git a/vendor/gix/src/config/snapshot/_impls.rs b/vendor/gix/src/config/snapshot/_impls.rs new file mode 100644 index 000000000..ec22cb640 --- /dev/null +++ b/vendor/gix/src/config/snapshot/_impls.rs @@ -0,0 +1,76 @@ +use std::{ + fmt::{Debug, Formatter}, + ops::{Deref, DerefMut}, +}; + +use crate::config::{CommitAutoRollback, Snapshot, SnapshotMut}; + +impl Debug for Snapshot<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.repo.config.resolved.to_string()) + } +} + +impl Debug for CommitAutoRollback<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.repo.as_ref().expect("still present").config.resolved.to_string()) + } +} + +impl Debug for SnapshotMut<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.config.to_string()) + } +} + +impl Drop for SnapshotMut<'_> { + fn drop(&mut self) { + if let Some(repo) = self.repo.take() { + self.commit_inner(repo).ok(); + }; + } +} + +impl Drop for CommitAutoRollback<'_> { + fn drop(&mut self) { + if let Some(repo) = self.repo.take() { + self.rollback_inner(repo).ok(); + } + } +} + +impl Deref for SnapshotMut<'_> { + type Target = gix_config::File<'static>; + + fn deref(&self) -> &Self::Target { + &self.config + } +} + +impl Deref for Snapshot<'_> { + type Target = gix_config::File<'static>; + + fn deref(&self) -> &Self::Target { + self.plumbing() + } +} + +impl Deref for CommitAutoRollback<'_> { + type Target = crate::Repository; + + fn deref(&self) -> &Self::Target { + self.repo.as_ref().expect("always present") + } +} + +impl DerefMut for CommitAutoRollback<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.repo.as_mut().expect("always present") + } +} + +impl DerefMut for SnapshotMut<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.config + } +} diff --git a/vendor/gix/src/config/snapshot/access.rs b/vendor/gix/src/config/snapshot/access.rs new file mode 100644 index 000000000..1710348a9 --- /dev/null +++ b/vendor/gix/src/config/snapshot/access.rs @@ -0,0 +1,143 @@ +#![allow(clippy::result_large_err)] +use std::borrow::Cow; + +use gix_features::threading::OwnShared; + +use crate::{ + bstr::BStr, + config::{CommitAutoRollback, Snapshot, SnapshotMut}, +}; + +/// Access configuration values, frozen in time, using a `key` which is a `.` separated string of up to +/// three tokens, namely `section_name.[subsection_name.]value_name`, like `core.bare` or `remote.origin.url`. +/// +/// Note that single-value methods always return the last value found, which is the one set most recently in the +/// hierarchy of configuration files, aka 'last one wins'. +impl<'repo> Snapshot<'repo> { + /// Return the boolean at `key`, or `None` if there is no such value or if the value can't be interpreted as + /// boolean. + /// + /// For a non-degenerating version, use [`try_boolean(…)`][Self::try_boolean()]. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + pub fn boolean<'a>(&self, key: impl Into<&'a BStr>) -> Option { + self.try_boolean(key).and_then(Result::ok) + } + + /// Like [`boolean()`][Self::boolean()], but it will report an error if the value couldn't be interpreted as boolean. + pub fn try_boolean<'a>(&self, key: impl Into<&'a BStr>) -> Option> { + self.repo.config.resolved.boolean_by_key(key) + } + + /// Return the resolved integer at `key`, or `None` if there is no such value or if the value can't be interpreted as + /// integer or exceeded the value range. + /// + /// For a non-degenerating version, use [`try_integer(…)`][Self::try_integer()]. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + pub fn integer<'a>(&self, key: impl Into<&'a BStr>) -> Option { + self.try_integer(key).and_then(Result::ok) + } + + /// Like [`integer()`][Self::integer()], but it will report an error if the value couldn't be interpreted as boolean. + pub fn try_integer<'a>(&self, key: impl Into<&'a BStr>) -> Option> { + self.repo.config.resolved.integer_by_key(key) + } + + /// Return the string at `key`, or `None` if there is no such value. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + pub fn string<'a>(&self, key: impl Into<&'a BStr>) -> Option> { + self.repo.config.resolved.string_by_key(key) + } + + /// Return the trusted and fully interpolated path at `key`, or `None` if there is no such value + /// or if no value was found in a trusted file. + /// An error occurs if the path could not be interpolated to its final value. + pub fn trusted_path<'a>( + &self, + key: impl Into<&'a BStr>, + ) -> Option, gix_config::path::interpolate::Error>> { + let key = gix_config::parse::key(key)?; + self.repo + .config + .trusted_file_path(key.section_name, key.subsection_name, key.value_name) + } +} + +/// Utilities and additional access +impl<'repo> Snapshot<'repo> { + /// Returns the underlying configuration implementation for a complete API, despite being a little less convenient. + /// + /// It's expected that more functionality will move up depending on demand. + pub fn plumbing(&self) -> &gix_config::File<'static> { + &self.repo.config.resolved + } +} + +/// Utilities +impl<'repo> SnapshotMut<'repo> { + /// Append configuration values of the form `core.abbrev=5` or `remote.origin.url = foo` or `core.bool-implicit-true` + /// to the end of the repository configuration, with each section marked with the given `source`. + /// + /// Note that doing so applies the configuration at the very end, so it will always override what came before it + /// even though the `source` is of lower priority as what's there. + pub fn append_config( + &mut self, + values: impl IntoIterator>, + source: gix_config::Source, + ) -> Result<&mut Self, crate::config::overrides::Error> { + crate::config::overrides::append(&mut self.config, values, source, |v| Some(format!("-c {v}").into()))?; + Ok(self) + } + /// Apply all changes made to this instance. + /// + /// Note that this would also happen once this instance is dropped, but using this method may be more intuitive and won't squelch errors + /// in case the new configuration is partially invalid. + pub fn commit(mut self) -> Result<&'repo mut crate::Repository, crate::config::Error> { + let repo = self.repo.take().expect("always present here"); + self.commit_inner(repo) + } + + pub(crate) fn commit_inner( + &mut self, + repo: &'repo mut crate::Repository, + ) -> Result<&'repo mut crate::Repository, crate::config::Error> { + repo.reread_values_and_clear_caches_replacing_config(std::mem::take(&mut self.config).into())?; + Ok(repo) + } + + /// Create a structure the temporarily commits the changes, but rolls them back when dropped. + pub fn commit_auto_rollback(mut self) -> Result, crate::config::Error> { + let repo = self.repo.take().expect("this only runs once on consumption"); + let prev_config = OwnShared::clone(&repo.config.resolved); + + Ok(CommitAutoRollback { + repo: self.commit_inner(repo)?.into(), + prev_config, + }) + } + + /// Don't apply any of the changes after consuming this instance, effectively forgetting them, returning the changed configuration. + pub fn forget(mut self) -> gix_config::File<'static> { + self.repo.take(); + std::mem::take(&mut self.config) + } +} + +/// Utilities +impl<'repo> CommitAutoRollback<'repo> { + /// Rollback the changes previously applied and all values before the change. + pub fn rollback(mut self) -> Result<&'repo mut crate::Repository, crate::config::Error> { + let repo = self.repo.take().expect("still present, consumed only once"); + self.rollback_inner(repo) + } + + pub(crate) fn rollback_inner( + &mut self, + repo: &'repo mut crate::Repository, + ) -> Result<&'repo mut crate::Repository, crate::config::Error> { + repo.reread_values_and_clear_caches_replacing_config(OwnShared::clone(&self.prev_config))?; + Ok(repo) + } +} diff --git a/vendor/gix/src/config/snapshot/credential_helpers.rs b/vendor/gix/src/config/snapshot/credential_helpers.rs new file mode 100644 index 000000000..5a07e9fe2 --- /dev/null +++ b/vendor/gix/src/config/snapshot/credential_helpers.rs @@ -0,0 +1,183 @@ +use std::{borrow::Cow, convert::TryFrom}; + +pub use error::Error; + +use crate::{ + bstr::{ByteSlice, ByteVec}, + config::{ + tree::{credential, Core, Credential, Key}, + Snapshot, + }, +}; + +mod error { + use crate::bstr::BString; + + /// The error returned by [Snapshot::credential_helpers()][super::Snapshot::credential_helpers()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Could not parse 'useHttpPath' key in section {section}")] + InvalidUseHttpPath { + section: BString, + source: gix_config::value::Error, + }, + #[error("core.askpass could not be read")] + CoreAskpass(#[from] gix_config::path::interpolate::Error), + } +} + +impl Snapshot<'_> { + /// Returns the configuration for all git-credential helpers from trusted configuration that apply + /// to the given `url` along with an action preconfigured to invoke the cascade with. + /// This includes `url` which may be altered to contain a user-name as configured. + /// + /// These can be invoked to obtain credentials. Note that the `url` is expected to be the one used + /// to connect to a remote, and thus should already have passed the url-rewrite engine. + /// + /// # Deviation + /// + /// - Invalid urls can't be used to obtain credential helpers as they are rejected early when creating a valid `url` here. + /// - Parsed urls will automatically drop the port if it's the default, i.e. `http://host:80` becomes `http://host` when parsed. + /// This affects the prompt provided to the user, so that git will use the verbatim url, whereas we use `http://host`. + /// - Upper-case scheme and host will be lower-cased automatically when parsing into a url, so prompts differ compared to git. + /// - A **difference in prompt might affect the matching of getting existing stored credentials**, and it's a question of this being + /// a feature or a bug. + // TODO: when dealing with `http.*.*` configuration, generalize this algorithm as needed and support precedence. + pub fn credential_helpers( + &self, + mut url: gix_url::Url, + ) -> Result< + ( + gix_credentials::helper::Cascade, + gix_credentials::helper::Action, + gix_prompt::Options<'static>, + ), + Error, + > { + let mut programs = Vec::new(); + let mut use_http_path = false; + let url_had_user_initially = url.user().is_some(); + normalize(&mut url); + + if let Some(credential_sections) = self + .repo + .config + .resolved + .sections_by_name_and_filter("credential", &mut self.repo.filter_config_section()) + { + for section in credential_sections { + let section = match section.header().subsection_name() { + Some(pattern) => gix_url::parse(pattern).ok().and_then(|mut pattern| { + normalize(&mut pattern); + let is_http = matches!(pattern.scheme, gix_url::Scheme::Https | gix_url::Scheme::Http); + let scheme = &pattern.scheme; + let host = pattern.host(); + let ports = is_http + .then(|| (pattern.port_or_default(), url.port_or_default())) + .unwrap_or((pattern.port, url.port)); + let path = (!(is_http && pattern.path_is_root())).then_some(&pattern.path); + + if !path.map_or(true, |path| path == &url.path) { + return None; + } + if pattern.user().is_some() && pattern.user() != url.user() { + return None; + } + (scheme == &url.scheme && host_matches(host, url.host()) && ports.0 == ports.1).then_some(( + section, + &credential::UrlParameter::HELPER, + &credential::UrlParameter::USERNAME, + &credential::UrlParameter::USE_HTTP_PATH, + )) + }), + None => Some(( + section, + &Credential::HELPER, + &Credential::USERNAME, + &Credential::USE_HTTP_PATH, + )), + }; + if let Some((section, helper_key, username_key, use_http_path_key)) = section { + for value in section.values(helper_key.name) { + if value.trim().is_empty() { + programs.clear(); + } else { + programs.push(gix_credentials::Program::from_custom_definition(value.into_owned())); + } + } + if let Some(Some(user)) = (!url_had_user_initially).then(|| { + section + .value(username_key.name) + .filter(|n| !n.trim().is_empty()) + .and_then(|n| { + let n: Vec<_> = Cow::into_owned(n).into(); + n.into_string().ok() + }) + }) { + url.set_user(Some(user)); + } + if let Some(toggle) = section + .value(use_http_path_key.name) + .map(|val| { + gix_config::Boolean::try_from(val) + .map_err(|err| Error::InvalidUseHttpPath { + source: err, + section: section.header().to_bstring(), + }) + .map(|b| b.0) + }) + .transpose()? + { + use_http_path = toggle; + } + } + } + } + + let allow_git_env = self.repo.options.permissions.env.git_prefix.is_allowed(); + let allow_ssh_env = self.repo.options.permissions.env.ssh_prefix.is_allowed(); + let prompt_options = gix_prompt::Options { + askpass: self + .trusted_path(Core::ASKPASS.logical_name().as_str()) + .transpose()? + .map(|c| Cow::Owned(c.into_owned())), + ..Default::default() + } + .apply_environment(allow_git_env, allow_ssh_env, allow_git_env); + Ok(( + gix_credentials::helper::Cascade { + programs, + use_http_path, + // The default ssh implementation uses binaries that do their own auth, so our passwords aren't used. + query_user_only: url.scheme == gix_url::Scheme::Ssh, + ..Default::default() + }, + gix_credentials::helper::Action::get_for_url(url.to_bstring()), + prompt_options, + )) + } +} + +fn host_matches(pattern: Option<&str>, host: Option<&str>) -> bool { + match (pattern, host) { + (Some(pattern), Some(host)) => { + let lfields = pattern.split('.'); + let rfields = host.split('.'); + if lfields.clone().count() != rfields.clone().count() { + return false; + } + lfields + .zip(rfields) + .all(|(pat, value)| gix_glob::wildmatch(pat.into(), value.into(), gix_glob::wildmatch::Mode::empty())) + } + (None, None) => true, + (Some(_), None) | (None, Some(_)) => false, + } +} + +fn normalize(url: &mut gix_url::Url) { + if !url.path_is_root() && url.path.ends_with(b"/") { + url.path.pop(); + } +} diff --git a/vendor/gix/src/config/snapshot/mod.rs b/vendor/gix/src/config/snapshot/mod.rs new file mode 100644 index 000000000..80ec6f948 --- /dev/null +++ b/vendor/gix/src/config/snapshot/mod.rs @@ -0,0 +1,5 @@ +mod _impls; +mod access; + +/// +pub mod credential_helpers; diff --git a/vendor/gix/src/config/tree/keys.rs b/vendor/gix/src/config/tree/keys.rs new file mode 100644 index 000000000..1cdd187d0 --- /dev/null +++ b/vendor/gix/src/config/tree/keys.rs @@ -0,0 +1,629 @@ +#![allow(clippy::result_large_err)] +use std::{ + borrow::Cow, + error::Error, + fmt::{Debug, Formatter}, +}; + +use crate::{ + bstr::BStr, + config, + config::tree::{Key, Link, Note, Section, SubSectionRequirement}, +}; + +/// Implements a value without any constraints, i.e. a any value. +pub struct Any { + /// The key of the value in the git configuration. + pub name: &'static str, + /// The parent section of the key. + pub section: &'static dyn Section, + /// The subsection requirement to use. + pub subsection_requirement: Option, + /// A link to other resources that might be eligible as value. + pub link: Option, + /// A note about this key. + pub note: Option, + /// The way validation and transformation should happen. + validate: T, +} + +/// Init +impl Any { + /// Create a new instance from `name` and `section` + pub const fn new(name: &'static str, section: &'static dyn Section) -> Self { + Any::new_with_validate(name, section, validate::All) + } +} + +/// Init other validate implementations +impl Any { + /// Create a new instance from `name` and `section` + pub const fn new_with_validate(name: &'static str, section: &'static dyn Section, validate: T) -> Self { + Any { + name, + section, + subsection_requirement: Some(SubSectionRequirement::Never), + link: None, + note: None, + validate, + } + } +} + +/// Builder +impl Any { + /// Set the subsection requirement to non-default values. + pub const fn with_subsection_requirement(mut self, requirement: Option) -> Self { + self.subsection_requirement = requirement; + self + } + + /// Associate an environment variable with this key. + /// + /// This is mainly useful for enriching error messages. + pub const fn with_environment_override(mut self, var: &'static str) -> Self { + self.link = Some(Link::EnvironmentOverride(var)); + self + } + + /// Set a link to another key which serves as fallback to provide a value if this key is not set. + pub const fn with_fallback(mut self, key: &'static dyn Key) -> Self { + self.link = Some(Link::FallbackKey(key)); + self + } + + /// Attach an informative message to this key. + pub const fn with_note(mut self, message: &'static str) -> Self { + self.note = Some(Note::Informative(message)); + self + } + + /// Inform about a deviation in how this key is interpreted. + pub const fn with_deviation(mut self, message: &'static str) -> Self { + self.note = Some(Note::Deviation(message)); + self + } +} + +/// Conversion +impl Any { + /// Try to convert `value` into a refspec suitable for the `op` operation. + pub fn try_into_refspec( + &'static self, + value: std::borrow::Cow<'_, BStr>, + op: gix_refspec::parse::Operation, + ) -> Result { + gix_refspec::parse(value.as_ref(), op) + .map(|spec| spec.to_owned()) + .map_err(|err| config::refspec::Error::from_value(self, value.into_owned()).with_source(err)) + } + + /// Try to interpret `value` as UTF-8 encoded string. + pub fn try_into_string(&'static self, value: Cow<'_, BStr>) -> Result { + use crate::bstr::ByteVec; + Vec::from(value.into_owned()).into_string().map_err(|err| { + let utf8_err = err.utf8_error().clone(); + config::string::Error::from_value(self, err.into_vec().into()).with_source(utf8_err) + }) + } +} + +impl Debug for Any { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.logical_name().fmt(f) + } +} + +impl std::fmt::Display for Any { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.logical_name()) + } +} + +impl Key for Any { + fn name(&self) -> &str { + self.name + } + + fn validate(&self, value: &BStr) -> Result<(), config::tree::key::validate::Error> { + Ok(self.validate.validate(value)?) + } + + fn section(&self) -> &dyn Section { + self.section + } + + fn subsection_requirement(&self) -> Option<&SubSectionRequirement> { + self.subsection_requirement.as_ref() + } + + fn link(&self) -> Option<&Link> { + self.link.as_ref() + } + + fn note(&self) -> Option<&Note> { + self.note.as_ref() + } +} + +/// A key which represents a date. +pub type Time = Any; + +/// The `core.(filesRefLockTimeout|packedRefsTimeout)` keys, or any other lock timeout for that matter. +pub type LockTimeout = Any; + +/// Keys specifying durations in milliseconds. +pub type DurationInMilliseconds = Any; + +/// A key which represents any unsigned integer. +pub type UnsignedInteger = Any; + +/// A key that represents a remote name, either as url or symbolic name. +pub type RemoteName = Any; + +/// A key that represents a boolean value. +pub type Boolean = Any; + +/// A key that represents an executable program, shell script or shell commands. +pub type Program = Any; + +/// A key that represents an executable program as identified by name or path. +pub type Executable = Any; + +/// A key that represents a path (to a resource). +pub type Path = Any; + +/// A key that represents a URL. +pub type Url = Any; + +/// A key that represents a UTF-8 string. +pub type String = Any; + +/// A key that represents a RefSpec for pushing. +pub type PushRefSpec = Any; + +/// A key that represents a RefSpec for fetching. +pub type FetchRefSpec = Any; + +mod duration { + use std::time::Duration; + + use crate::{ + config, + config::tree::{keys::DurationInMilliseconds, Section}, + }; + + impl DurationInMilliseconds { + /// Create a new instance. + pub const fn new_duration(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, super::validate::DurationInMilliseconds) + } + + /// Return a valid duration as parsed from an integer that is interpreted as milliseconds. + pub fn try_into_duration( + &'static self, + value: Result, + ) -> Result { + let value = value.map_err(|err| config::duration::Error::from(self).with_source(err))?; + Ok(match value { + val if val < 0 => Duration::from_secs(u64::MAX), + val => Duration::from_millis(val.try_into().expect("i64 to u64 always works if positive")), + }) + } + } +} + +mod lock_timeout { + use std::time::Duration; + + use gix_lock::acquire::Fail; + + use crate::{ + config, + config::tree::{keys::LockTimeout, Section}, + }; + + impl LockTimeout { + /// Create a new instance. + pub const fn new_lock_timeout(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, super::validate::LockTimeout) + } + + /// Return information on how long to wait for locked files. + pub fn try_into_lock_timeout( + &'static self, + value: Result, + ) -> Result { + let value = value.map_err(|err| config::lock_timeout::Error::from(self).with_source(err))?; + Ok(match value { + val if val < 0 => Fail::AfterDurationWithBackoff(Duration::from_secs(u64::MAX)), + val if val == 0 => Fail::Immediately, + val => Fail::AfterDurationWithBackoff(Duration::from_millis( + val.try_into().expect("i64 to u64 always works if positive"), + )), + }) + } + } +} + +mod refspecs { + use crate::config::tree::{ + keys::{validate, FetchRefSpec, PushRefSpec}, + Section, + }; + + impl PushRefSpec { + /// Create a new instance. + pub const fn new_push_refspec(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, validate::PushRefSpec) + } + } + + impl FetchRefSpec { + /// Create a new instance. + pub const fn new_fetch_refspec(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, validate::FetchRefSpec) + } + } +} + +mod url { + use std::borrow::Cow; + + use crate::{ + bstr::BStr, + config, + config::tree::{ + keys::{validate, Url}, + Section, + }, + }; + + impl Url { + /// Create a new instance. + pub const fn new_url(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, validate::Url) + } + + /// Try to parse `value` as URL. + pub fn try_into_url(&'static self, value: Cow<'_, BStr>) -> Result { + gix_url::parse(value.as_ref()) + .map_err(|err| config::url::Error::from_value(self, value.into_owned()).with_source(err)) + } + } +} + +impl String { + /// Create a new instance. + pub const fn new_string(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, validate::String) + } +} + +impl Program { + /// Create a new instance. + pub const fn new_program(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, validate::Program) + } +} + +impl Executable { + /// Create a new instance. + pub const fn new_executable(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, validate::Executable) + } +} + +impl Path { + /// Create a new instance. + pub const fn new_path(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, validate::Path) + } +} + +mod workers { + use crate::config::tree::{keys::UnsignedInteger, Section}; + + impl UnsignedInteger { + /// Create a new instance. + pub const fn new_unsigned_integer(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, super::validate::UnsignedInteger) + } + + /// Convert `value` into a `usize` or wrap it into a specialized error. + pub fn try_into_usize( + &'static self, + value: Result, + ) -> Result { + value + .map_err(|err| crate::config::unsigned_integer::Error::from(self).with_source(err)) + .and_then(|value| { + value + .try_into() + .map_err(|_| crate::config::unsigned_integer::Error::from(self)) + }) + } + + /// Convert `value` into a `u64` or wrap it into a specialized error. + pub fn try_into_u64( + &'static self, + value: Result, + ) -> Result { + value + .map_err(|err| crate::config::unsigned_integer::Error::from(self).with_source(err)) + .and_then(|value| { + value + .try_into() + .map_err(|_| crate::config::unsigned_integer::Error::from(self)) + }) + } + + /// Convert `value` into a `u32` or wrap it into a specialized error. + pub fn try_into_u32( + &'static self, + value: Result, + ) -> Result { + value + .map_err(|err| crate::config::unsigned_integer::Error::from(self).with_source(err)) + .and_then(|value| { + value + .try_into() + .map_err(|_| crate::config::unsigned_integer::Error::from(self)) + }) + } + } +} + +mod time { + use std::borrow::Cow; + + use crate::{ + bstr::{BStr, ByteSlice}, + config::tree::{ + keys::{validate, Time}, + Section, + }, + }; + + impl Time { + /// Create a new instance. + pub const fn new_time(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, validate::Time) + } + + /// Convert the `value` into a date if possible, with `now` as reference time for relative dates. + pub fn try_into_time( + &self, + value: Cow<'_, BStr>, + now: Option, + ) -> Result { + gix_date::parse( + value + .as_ref() + .to_str() + .map_err(|_| gix_date::parse::Error::InvalidDateString { + input: value.to_string(), + })?, + now, + ) + } + } +} + +mod boolean { + use crate::{ + config, + config::tree::{ + keys::{validate, Boolean}, + Section, + }, + }; + + impl Boolean { + /// Create a new instance. + pub const fn new_boolean(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, validate::Boolean) + } + + /// Process the `value` into a result with an improved error message. + /// + /// `value` is expected to be provided by [`gix_config::File::boolean()`]. + pub fn enrich_error( + &'static self, + value: Result, + ) -> Result { + value.map_err(|err| config::boolean::Error::from(self).with_source(err)) + } + } +} + +mod remote_name { + use std::borrow::Cow; + + use crate::{ + bstr::{BStr, BString}, + config, + config::tree::{keys::RemoteName, Section}, + }; + + impl RemoteName { + /// Create a new instance. + pub const fn new_remote_name(name: &'static str, section: &'static dyn Section) -> Self { + Self::new_with_validate(name, section, super::validate::RemoteName) + } + + /// Try to validate `name` as symbolic remote name and return it. + #[allow(clippy::result_large_err)] + pub fn try_into_symbolic_name( + &'static self, + name: Cow<'_, BStr>, + ) -> Result { + crate::remote::name::validated(name.into_owned()) + .map_err(|err| config::remote::symbolic_name::Error::from(self).with_source(err)) + } + } +} + +/// Provide a way to validate a value, or decode a value from `gix-config`. +pub trait Validate { + /// Validate `value` or return an error. + fn validate(&self, value: &BStr) -> Result<(), Box>; +} + +/// various implementations of the `Validate` trait. +pub mod validate { + use std::{borrow::Cow, error::Error}; + + use crate::{ + bstr::{BStr, ByteSlice}, + config::tree::keys::Validate, + remote, + }; + + /// Everything is valid. + #[derive(Default)] + pub struct All; + + impl Validate for All { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + Ok(()) + } + } + + /// Assure that values that parse as git dates are valid. + #[derive(Default)] + pub struct Time; + + impl Validate for Time { + fn validate(&self, value: &BStr) -> Result<(), Box> { + gix_date::parse(value.to_str()?, std::time::SystemTime::now().into())?; + Ok(()) + } + } + + /// Assure that values that parse as unsigned integers are valid. + #[derive(Default)] + pub struct UnsignedInteger; + + impl Validate for UnsignedInteger { + fn validate(&self, value: &BStr) -> Result<(), Box> { + usize::try_from( + gix_config::Integer::try_from(value)? + .to_decimal() + .ok_or_else(|| format!("integer {value} cannot be represented as `usize`"))?, + )?; + Ok(()) + } + } + + /// Assure that values that parse as git booleans are valid. + #[derive(Default)] + pub struct Boolean; + + impl Validate for Boolean { + fn validate(&self, value: &BStr) -> Result<(), Box> { + gix_config::Boolean::try_from(value)?; + Ok(()) + } + } + + /// Values that are git remotes, symbolic or urls + #[derive(Default)] + pub struct RemoteName; + impl Validate for RemoteName { + fn validate(&self, value: &BStr) -> Result<(), Box> { + remote::Name::try_from(Cow::Borrowed(value)) + .map_err(|_| format!("Illformed UTF-8 in remote name: \"{}\"", value.to_str_lossy()))?; + Ok(()) + } + } + + /// Values that are programs - everything is allowed. + #[derive(Default)] + pub struct Program; + impl Validate for Program { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + Ok(()) + } + } + + /// Values that are programs executables, everything is allowed. + #[derive(Default)] + pub struct Executable; + impl Validate for Executable { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + Ok(()) + } + } + + /// Values that parse as URLs. + #[derive(Default)] + pub struct Url; + impl Validate for Url { + fn validate(&self, value: &BStr) -> Result<(), Box> { + gix_url::parse(value)?; + Ok(()) + } + } + + /// Values that parse as ref-specs for pushing. + #[derive(Default)] + pub struct PushRefSpec; + impl Validate for PushRefSpec { + fn validate(&self, value: &BStr) -> Result<(), Box> { + gix_refspec::parse(value, gix_refspec::parse::Operation::Push)?; + Ok(()) + } + } + + /// Values that parse as ref-specs for pushing. + #[derive(Default)] + pub struct FetchRefSpec; + impl Validate for FetchRefSpec { + fn validate(&self, value: &BStr) -> Result<(), Box> { + gix_refspec::parse(value, gix_refspec::parse::Operation::Fetch)?; + Ok(()) + } + } + + /// Timeouts used for file locks. + pub struct LockTimeout; + impl Validate for LockTimeout { + fn validate(&self, value: &BStr) -> Result<(), Box> { + let value = gix_config::Integer::try_from(value)? + .to_decimal() + .ok_or_else(|| format!("integer {value} cannot be represented as integer")); + super::super::Core::FILES_REF_LOCK_TIMEOUT.try_into_lock_timeout(Ok(value?))?; + Ok(()) + } + } + + /// Durations in milliseconds. + pub struct DurationInMilliseconds; + impl Validate for DurationInMilliseconds { + fn validate(&self, value: &BStr) -> Result<(), Box> { + let value = gix_config::Integer::try_from(value)? + .to_decimal() + .ok_or_else(|| format!("integer {value} cannot be represented as integer")); + super::super::gitoxide::Http::CONNECT_TIMEOUT.try_into_duration(Ok(value?))?; + Ok(()) + } + } + + /// A UTF-8 string. + pub struct String; + impl Validate for String { + fn validate(&self, value: &BStr) -> Result<(), Box> { + value.to_str()?; + Ok(()) + } + } + + /// Any path - everything is allowed. + pub struct Path; + impl Validate for Path { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/mod.rs b/vendor/gix/src/config/tree/mod.rs new file mode 100644 index 000000000..fd769f3ed --- /dev/null +++ b/vendor/gix/src/config/tree/mod.rs @@ -0,0 +1,123 @@ +//! The tree of supported configuration values for use in [`config_overrides`][crate::open::Options::config_overrides()] +//! or for validating and transforming well-known configuration values. +//! +//! It can also be used to traverse all implemented keys and to validate values before usage as configuration overrides. +//! +//! ### Leniency +//! +//! When validating values, we don't apply leniency here which is left to the caller. Leniency is an application defined configuration +//! to ignore errors on non-security related values, which might make applications more resilient towards misconfiguration. +pub(crate) mod root { + use super::sections; + use crate::config::tree::Section; + + /// The root of the configuration tree, suitable to discover all sub-sections at runtime or compile time. + #[derive(Copy, Clone, Default)] + pub struct Tree; + + impl Tree { + /// The `author` section. + pub const AUTHOR: sections::Author = sections::Author; + /// The `branch` section. + pub const BRANCH: sections::Branch = sections::Branch; + /// The `checkout` section. + pub const CHECKOUT: sections::Checkout = sections::Checkout; + /// The `clone` section. + pub const CLONE: sections::Clone = sections::Clone; + /// The `committer` section. + pub const COMMITTER: sections::Committer = sections::Committer; + /// The `core` section. + pub const CORE: sections::Core = sections::Core; + /// The `credential` section. + pub const CREDENTIAL: sections::Credential = sections::Credential; + /// The `diff` section. + pub const DIFF: sections::Diff = sections::Diff; + /// The `extensions` section. + pub const EXTENSIONS: sections::Extensions = sections::Extensions; + /// The `gitoxide` section. + pub const GITOXIDE: sections::Gitoxide = sections::Gitoxide; + /// The `http` section. + pub const HTTP: sections::Http = sections::Http; + /// The `init` section. + pub const INIT: sections::Init = sections::Init; + /// The `pack` section. + pub const PACK: sections::Pack = sections::Pack; + /// The `protocol` section. + pub const PROTOCOL: sections::Protocol = sections::Protocol; + /// The `remote` section. + pub const REMOTE: sections::Remote = sections::Remote; + /// The `safe` section. + pub const SAFE: sections::Safe = sections::Safe; + /// The `ssh` section. + pub const SSH: sections::Ssh = sections::Ssh; + /// The `user` section. + pub const USER: sections::User = sections::User; + /// The `url` section. + pub const URL: sections::Url = sections::Url; + + /// List all available sections. + pub fn sections(&self) -> &[&dyn Section] { + &[ + &Self::AUTHOR, + &Self::BRANCH, + &Self::CHECKOUT, + &Self::CLONE, + &Self::COMMITTER, + &Self::CORE, + &Self::CREDENTIAL, + &Self::DIFF, + &Self::EXTENSIONS, + &Self::GITOXIDE, + &Self::HTTP, + &Self::INIT, + &Self::PACK, + &Self::PROTOCOL, + &Self::REMOTE, + &Self::SAFE, + &Self::SSH, + &Self::USER, + &Self::URL, + ] + } + } +} + +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, +}; + +/// Generic value implementations for static instantiation. +pub mod keys; + +/// +pub mod key { + /// + pub mod validate { + /// The error returned by [Key::validate()][crate::config::tree::Key::validate()]. + #[derive(Debug, thiserror::Error)] + #[error(transparent)] + #[allow(missing_docs)] + pub struct Error { + #[from] + source: Box, + } + } + /// + pub mod validate_assignment { + /// The error returned by [Key::validated_assignment*()][crate::config::tree::Key::validated_assignment_fmt()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Failed to validate the value to be assigned to this key")] + Validate(#[from] super::validate::Error), + #[error("{message}")] + Name { message: String }, + } + } +} + +mod traits; +pub use traits::{Key, Link, Note, Section, SubSectionRequirement}; diff --git a/vendor/gix/src/config/tree/sections/author.rs b/vendor/gix/src/config/tree/sections/author.rs new file mode 100644 index 000000000..4101e3817 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/author.rs @@ -0,0 +1,23 @@ +use crate::{ + config, + config::tree::{gitoxide, keys, Author, Key, Section}, +}; + +impl Author { + /// The `author.name` key. + pub const NAME: keys::Any = + keys::Any::new("name", &config::Tree::AUTHOR).with_fallback(&gitoxide::Author::NAME_FALLBACK); + /// The `author.email` key. + pub const EMAIL: keys::Any = + keys::Any::new("email", &config::Tree::AUTHOR).with_fallback(&gitoxide::Author::EMAIL_FALLBACK); +} + +impl Section for Author { + fn name(&self) -> &str { + "author" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::NAME, &Self::EMAIL] + } +} diff --git a/vendor/gix/src/config/tree/sections/branch.rs b/vendor/gix/src/config/tree/sections/branch.rs new file mode 100644 index 000000000..8e1e0a4b8 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/branch.rs @@ -0,0 +1,65 @@ +use crate::config::tree::{keys, traits::SubSectionRequirement, Branch, Key, Section}; + +const NAME_PARAMETER: Option = Some(SubSectionRequirement::Parameter("name")); + +impl Branch { + /// The `branch..merge` key. + pub const MERGE: Merge = Merge::new_with_validate("merge", &crate::config::Tree::BRANCH, validate::FullNameRef) + .with_subsection_requirement(NAME_PARAMETER); + /// The `branch..pushRemote` key. + pub const PUSH_REMOTE: keys::RemoteName = + keys::RemoteName::new_remote_name("pushRemote", &crate::config::Tree::BRANCH) + .with_subsection_requirement(NAME_PARAMETER); + /// The `branch..remote` key. + pub const REMOTE: keys::RemoteName = keys::RemoteName::new_remote_name("remote", &crate::config::Tree::BRANCH) + .with_subsection_requirement(NAME_PARAMETER); +} + +impl Section for Branch { + fn name(&self) -> &str { + "branch" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::MERGE, &Self::PUSH_REMOTE, &Self::REMOTE] + } +} + +/// The `branch..merge` key. +pub type Merge = keys::Any; + +mod merge { + use std::borrow::Cow; + + use gix_ref::FullNameRef; + + use crate::{bstr::BStr, config::tree::branch::Merge}; + + impl Merge { + /// Return the validated full ref name from `value` if it is valid. + pub fn try_into_fullrefname( + value: Cow<'_, BStr>, + ) -> Result, gix_validate::reference::name::Error> { + match value { + Cow::Borrowed(v) => v.try_into().map(Cow::Borrowed), + Cow::Owned(v) => v.try_into().map(Cow::Owned), + } + } + } +} + +/// +pub mod validate { + use crate::{ + bstr::BStr, + config::tree::{branch::Merge, keys}, + }; + + pub struct FullNameRef; + impl keys::Validate for FullNameRef { + fn validate(&self, value: &BStr) -> Result<(), Box> { + Merge::try_into_fullrefname(value.into())?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/checkout.rs b/vendor/gix/src/config/tree/sections/checkout.rs new file mode 100644 index 000000000..27f31ee84 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/checkout.rs @@ -0,0 +1,58 @@ +use crate::{ + config, + config::tree::{keys, Checkout, Key, Section}, +}; + +impl Checkout { + /// The `checkout.workers` key. + pub const WORKERS: Workers = Workers::new_with_validate("workers", &config::Tree::CHECKOUT, validate::Workers) + .with_deviation("if unset, uses all cores instead of just one"); +} + +/// The `checkout.workers` key. +pub type Workers = keys::Any; + +impl Section for Checkout { + fn name(&self) -> &str { + "checkout" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::WORKERS] + } +} + +mod workers { + use crate::config::tree::checkout::Workers; + + impl Workers { + /// Return the amount of threads to use for checkout, with `0` meaning all available ones, after decoding our integer value from `config`, + /// or `None` if the value isn't set which is typically interpreted as "as many threads as available" + pub fn try_from_workers( + &'static self, + value: Result, + ) -> Result { + match value { + Ok(v) if v < 0 => Ok(0), + Ok(v) => Ok(v.try_into().expect("positive i64 can always be usize on 64 bit")), + Err(err) => Err(crate::config::key::Error::from(&super::Checkout::WORKERS).with_source(err)), + } + } + } +} + +/// +pub mod validate { + use crate::{bstr::BStr, config::tree::keys}; + + pub struct Workers; + impl keys::Validate for Workers { + fn validate(&self, value: &BStr) -> Result<(), Box> { + super::Checkout::WORKERS.try_from_workers(gix_config::Integer::try_from(value).and_then(|i| { + i.to_decimal() + .ok_or_else(|| gix_config::value::Error::new("Integer overflow", value.to_owned())) + }))?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/clone.rs b/vendor/gix/src/config/tree/sections/clone.rs new file mode 100644 index 000000000..616185a0b --- /dev/null +++ b/vendor/gix/src/config/tree/sections/clone.rs @@ -0,0 +1,20 @@ +use crate::{ + config, + config::tree::{keys, Clone, Key, Section}, +}; + +impl Clone { + /// The `clone.defaultRemoteName` key. + pub const DEFAULT_REMOTE_NAME: keys::RemoteName = + keys::RemoteName::new_remote_name("defaultRemoteName", &config::Tree::CLONE); +} + +impl Section for Clone { + fn name(&self) -> &str { + "clone" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::DEFAULT_REMOTE_NAME] + } +} diff --git a/vendor/gix/src/config/tree/sections/committer.rs b/vendor/gix/src/config/tree/sections/committer.rs new file mode 100644 index 000000000..acc25c930 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/committer.rs @@ -0,0 +1,23 @@ +use crate::{ + config, + config::tree::{gitoxide, keys, Committer, Key, Section}, +}; + +impl Committer { + /// The `committer.name` key. + pub const NAME: keys::Any = + keys::Any::new("name", &config::Tree::COMMITTER).with_fallback(&gitoxide::Committer::NAME_FALLBACK); + /// The `committer.email` key. + pub const EMAIL: keys::Any = + keys::Any::new("email", &config::Tree::COMMITTER).with_fallback(&gitoxide::Committer::EMAIL_FALLBACK); +} + +impl Section for Committer { + fn name(&self) -> &str { + "committer" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::NAME, &Self::EMAIL] + } +} diff --git a/vendor/gix/src/config/tree/sections/core.rs b/vendor/gix/src/config/tree/sections/core.rs new file mode 100644 index 000000000..6ea0580e1 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/core.rs @@ -0,0 +1,302 @@ +use crate::{ + config, + config::tree::{keys, Core, Key, Section}, +}; + +impl Core { + /// The `core.abbrev` key. + pub const ABBREV: Abbrev = Abbrev::new_with_validate("abbrev", &config::Tree::CORE, validate::Abbrev); + /// The `core.bare` key. + pub const BARE: keys::Boolean = keys::Boolean::new_boolean("bare", &config::Tree::CORE); + /// The `core.checkStat` key. + pub const CHECK_STAT: CheckStat = + CheckStat::new_with_validate("checkStat", &config::Tree::CORE, validate::CheckStat); + /// The `core.deltaBaseCacheLimit` key. + pub const DELTA_BASE_CACHE_LIMIT: keys::UnsignedInteger = + keys::UnsignedInteger::new_unsigned_integer("deltaBaseCacheLimit", &config::Tree::CORE) + .with_environment_override("GITOXIDE_PACK_CACHE_MEMORY") + .with_note("if unset, we default to a small 64 slot fixed-size cache that holds at most 64 full delta base objects of any size. Set to 0 to deactivate it entirely"); + /// The `core.disambiguate` key. + pub const DISAMBIGUATE: Disambiguate = + Disambiguate::new_with_validate("disambiguate", &config::Tree::CORE, validate::Disambiguate); + /// The `core.fileMode` key. + pub const FILE_MODE: keys::Boolean = keys::Boolean::new_boolean("fileMode", &config::Tree::CORE); + /// The `core.ignoreCase` key. + pub const IGNORE_CASE: keys::Boolean = keys::Boolean::new_boolean("ignoreCase", &config::Tree::CORE); + /// The `core.filesRefLockTimeout` key. + pub const FILES_REF_LOCK_TIMEOUT: keys::LockTimeout = + keys::LockTimeout::new_lock_timeout("filesRefLockTimeout", &config::Tree::CORE); + /// The `core.packedRefsTimeout` key. + pub const PACKED_REFS_TIMEOUT: keys::LockTimeout = + keys::LockTimeout::new_lock_timeout("packedRefsTimeout", &config::Tree::CORE); + /// The `core.multiPackIndex` key. + pub const MULTIPACK_INDEX: keys::Boolean = keys::Boolean::new_boolean("multiPackIndex", &config::Tree::CORE); + /// The `core.logAllRefUpdates` key. + pub const LOG_ALL_REF_UPDATES: LogAllRefUpdates = + LogAllRefUpdates::new_with_validate("logAllRefUpdates", &config::Tree::CORE, validate::LogAllRefUpdates); + /// The `core.precomposeUnicode` key. + /// + /// Needs application to use [env::args_os][crate::env::args_os()] to conform all input paths before they are used. + pub const PRECOMPOSE_UNICODE: keys::Boolean = keys::Boolean::new_boolean("precomposeUnicode", &config::Tree::CORE) + .with_note("application needs to conform all program input by using gix::env::args_os()"); + /// The `core.repositoryFormatVersion` key. + pub const REPOSITORY_FORMAT_VERSION: keys::UnsignedInteger = + keys::UnsignedInteger::new_unsigned_integer("repositoryFormatVersion", &config::Tree::CORE); + /// The `core.symlinks` key. + pub const SYMLINKS: keys::Boolean = keys::Boolean::new_boolean("symlinks", &config::Tree::CORE); + /// The `core.trustCTime` key. + pub const TRUST_C_TIME: keys::Boolean = keys::Boolean::new_boolean("trustCTime", &config::Tree::CORE); + /// The `core.worktree` key. + pub const WORKTREE: keys::Any = keys::Any::new("worktree", &config::Tree::CORE) + .with_environment_override("GIT_WORK_TREE") + .with_deviation("Overriding the worktree with environment variables is supported using `ThreadSafeRepository::open_with_environment_overrides()"); + /// The `core.askPass` key. + pub const ASKPASS: keys::Executable = keys::Executable::new_executable("askPass", &config::Tree::CORE) + .with_environment_override("GIT_ASKPASS") + .with_note("fallback is 'SSH_ASKPASS'"); + /// The `core.excludesFile` key. + pub const EXCLUDES_FILE: keys::Executable = keys::Executable::new_executable("excludesFile", &config::Tree::CORE); + /// The `core.attributesFile` key. + pub const ATTRIBUTES_FILE: keys::Executable = + keys::Executable::new_executable("attributesFile", &config::Tree::CORE) + .with_deviation("for checkout - it's already queried but needs building of attributes group, and of course support during checkout"); + /// The `core.sshCommand` key. + pub const SSH_COMMAND: keys::Executable = keys::Executable::new_executable("sshCommand", &config::Tree::CORE) + .with_environment_override("GIT_SSH_COMMAND"); +} + +impl Section for Core { + fn name(&self) -> &str { + "core" + } + + fn keys(&self) -> &[&dyn Key] { + &[ + &Self::ABBREV, + &Self::BARE, + &Self::CHECK_STAT, + &Self::DELTA_BASE_CACHE_LIMIT, + &Self::DISAMBIGUATE, + &Self::FILE_MODE, + &Self::IGNORE_CASE, + &Self::FILES_REF_LOCK_TIMEOUT, + &Self::PACKED_REFS_TIMEOUT, + &Self::MULTIPACK_INDEX, + &Self::LOG_ALL_REF_UPDATES, + &Self::PRECOMPOSE_UNICODE, + &Self::REPOSITORY_FORMAT_VERSION, + &Self::SYMLINKS, + &Self::TRUST_C_TIME, + &Self::WORKTREE, + &Self::ASKPASS, + &Self::EXCLUDES_FILE, + &Self::ATTRIBUTES_FILE, + &Self::SSH_COMMAND, + ] + } +} + +/// The `core.checkStat` key. +pub type CheckStat = keys::Any; + +/// The `core.abbrev` key. +pub type Abbrev = keys::Any; + +/// The `core.logAllRefUpdates` key. +pub type LogAllRefUpdates = keys::Any; + +/// The `core.disambiguate` key. +pub type Disambiguate = keys::Any; + +mod disambiguate { + use std::borrow::Cow; + + use crate::{ + bstr::{BStr, ByteSlice}, + config, + config::tree::core::Disambiguate, + revision::spec::parse::ObjectKindHint, + }; + + impl Disambiguate { + /// Convert a disambiguation marker into the respective enum. + pub fn try_into_object_kind_hint( + &'static self, + value: Cow<'_, BStr>, + ) -> Result, config::key::GenericErrorWithValue> { + let hint = match value.as_ref().as_bytes() { + b"none" => return Ok(None), + b"commit" => ObjectKindHint::Commit, + b"committish" => ObjectKindHint::Committish, + b"tree" => ObjectKindHint::Tree, + b"treeish" => ObjectKindHint::Treeish, + b"blob" => ObjectKindHint::Blob, + _ => return Err(config::key::GenericErrorWithValue::from_value(self, value.into_owned())), + }; + Ok(Some(hint)) + } + } +} + +mod log_all_ref_updates { + use std::borrow::Cow; + + use crate::{bstr::BStr, config, config::tree::core::LogAllRefUpdates}; + + impl LogAllRefUpdates { + /// Returns the mode for ref-updates as parsed from `value`. If `value` is not a boolean, `string_on_failure` will be called + /// to obtain the key `core.logAllRefUpdates` as string instead. For correctness, this two step process is necessary as + /// the interpretation of booleans in special in `gix-config`, i.e. we can't just treat it as string. + pub fn try_into_ref_updates<'a>( + &'static self, + value: Option>, + string_on_failure: impl FnOnce() -> Option>, + ) -> Result, config::key::GenericErrorWithValue> { + match value.transpose().ok().flatten() { + Some(bool) => Ok(Some(if bool { + gix_ref::store::WriteReflog::Normal + } else { + gix_ref::store::WriteReflog::Disable + })), + None => match string_on_failure() { + Some(val) if val.eq_ignore_ascii_case(b"always") => Ok(Some(gix_ref::store::WriteReflog::Always)), + Some(val) => Err(config::key::GenericErrorWithValue::from_value(self, val.into_owned())), + None => Ok(None), + }, + } + } + } +} + +mod check_stat { + use std::borrow::Cow; + + use crate::{ + bstr::{BStr, ByteSlice}, + config, + config::tree::core::CheckStat, + }; + + impl CheckStat { + /// Returns true if the full set of stat entries should be checked, and it's just as lenient as git. + pub fn try_into_checkstat( + &'static self, + value: Cow<'_, BStr>, + ) -> Result { + Ok(match value.as_ref().as_bytes() { + b"minimal" => false, + b"default" => true, + _ => { + return Err(config::key::GenericErrorWithValue::from_value(self, value.into_owned())); + } + }) + } + } +} + +mod abbrev { + use std::borrow::Cow; + + use config::abbrev::Error; + + use crate::{ + bstr::{BStr, ByteSlice}, + config, + config::tree::core::Abbrev, + }; + + impl Abbrev { + /// Convert the given `hex_len_str` into the amount of characters that a short hash should have. + /// If `None` is returned, the correct value can be determined based on the amount of objects in the repo. + pub fn try_into_abbreviation( + &'static self, + hex_len_str: Cow<'_, BStr>, + object_hash: gix_hash::Kind, + ) -> Result, Error> { + let max = object_hash.len_in_hex() as u8; + if hex_len_str.trim().is_empty() { + return Err(Error { + value: hex_len_str.into_owned(), + max, + }); + } + if hex_len_str.trim().eq_ignore_ascii_case(b"auto") { + Ok(None) + } else { + let value_bytes = hex_len_str.as_ref(); + if let Ok(false) = gix_config::Boolean::try_from(value_bytes).map(Into::into) { + Ok(object_hash.len_in_hex().into()) + } else { + let value = gix_config::Integer::try_from(value_bytes) + .map_err(|_| Error { + value: hex_len_str.clone().into_owned(), + max, + })? + .to_decimal() + .ok_or_else(|| Error { + value: hex_len_str.clone().into_owned(), + max, + })?; + if value < 4 || value as usize > object_hash.len_in_hex() { + return Err(Error { + value: hex_len_str.clone().into_owned(), + max, + }); + } + Ok(Some(value as usize)) + } + } + } + } +} + +mod validate { + use crate::{bstr::BStr, config::tree::keys}; + + pub struct LockTimeout; + impl keys::Validate for LockTimeout { + fn validate(&self, value: &BStr) -> Result<(), Box> { + let value = gix_config::Integer::try_from(value)? + .to_decimal() + .ok_or_else(|| format!("integer {value} cannot be represented as integer")); + super::Core::FILES_REF_LOCK_TIMEOUT.try_into_lock_timeout(Ok(value?))?; + Ok(()) + } + } + + pub struct Disambiguate; + impl keys::Validate for Disambiguate { + fn validate(&self, value: &BStr) -> Result<(), Box> { + super::Core::DISAMBIGUATE.try_into_object_kind_hint(value.into())?; + Ok(()) + } + } + + pub struct LogAllRefUpdates; + impl keys::Validate for LogAllRefUpdates { + fn validate(&self, value: &BStr) -> Result<(), Box> { + super::Core::LOG_ALL_REF_UPDATES + .try_into_ref_updates(Some(gix_config::Boolean::try_from(value).map(|b| b.0)), || { + Some(value.into()) + })?; + Ok(()) + } + } + + pub struct CheckStat; + impl keys::Validate for CheckStat { + fn validate(&self, value: &BStr) -> Result<(), Box> { + super::Core::CHECK_STAT.try_into_checkstat(value.into())?; + Ok(()) + } + } + + pub struct Abbrev; + impl keys::Validate for Abbrev { + fn validate(&self, value: &BStr) -> Result<(), Box> { + // TODO: when there is options, validate against all hashes and assure all fail to trigger a validation failure. + super::Core::ABBREV.try_into_abbreviation(value.into(), gix_hash::Kind::Sha1)?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/credential.rs b/vendor/gix/src/config/tree/sections/credential.rs new file mode 100644 index 000000000..d370db0c5 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/credential.rs @@ -0,0 +1,56 @@ +use crate::{ + config, + config::tree::{keys, Credential, Key, Section}, +}; + +impl Credential { + /// The `credential.helper` key. + pub const HELPER: keys::Program = keys::Program::new_program("helper", &config::Tree::CREDENTIAL); + /// The `credential.username` key. + pub const USERNAME: keys::Any = keys::Any::new("username", &config::Tree::CREDENTIAL); + /// The `credential.useHttpPath` key. + pub const USE_HTTP_PATH: keys::Boolean = keys::Boolean::new_boolean("useHttpPath", &config::Tree::CREDENTIAL); + + /// The `credential.` subsection + pub const URL_PARAMETER: UrlParameter = UrlParameter; +} + +/// The `credential.` parameter section. +pub struct UrlParameter; + +impl UrlParameter { + /// The `credential..helper` key. + pub const HELPER: keys::Program = keys::Program::new_program("helper", &Credential::URL_PARAMETER); + /// The `credential..username` key. + pub const USERNAME: keys::Any = keys::Any::new("username", &Credential::URL_PARAMETER); + /// The `credential..useHttpPath` key. + pub const USE_HTTP_PATH: keys::Boolean = keys::Boolean::new_boolean("useHttpPath", &Credential::URL_PARAMETER); +} + +impl Section for UrlParameter { + fn name(&self) -> &str { + "" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::HELPER, &Self::USERNAME, &Self::USE_HTTP_PATH] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&config::Tree::CREDENTIAL) + } +} + +impl Section for Credential { + fn name(&self) -> &str { + "credential" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::HELPER, &Self::USERNAME, &Self::USE_HTTP_PATH] + } + + fn sub_sections(&self) -> &[&dyn Section] { + &[&Self::URL_PARAMETER] + } +} diff --git a/vendor/gix/src/config/tree/sections/diff.rs b/vendor/gix/src/config/tree/sections/diff.rs new file mode 100644 index 000000000..103bb7001 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/diff.rs @@ -0,0 +1,133 @@ +use crate::{ + config, + config::tree::{keys, Diff, Key, Section}, +}; + +impl Diff { + /// The `diff.algorithm` key. + pub const ALGORITHM: Algorithm = Algorithm::new_with_validate("algorithm", &config::Tree::DIFF, validate::Algorithm) + .with_deviation("'patience' diff is not implemented and can default to 'histogram' if lenient config is used, and defaults to histogram if unset for fastest and best results"); + /// The `diff.renameLimit` key. + pub const RENAME_LIMIT: keys::UnsignedInteger = keys::UnsignedInteger::new_unsigned_integer( + "renameLimit", + &config::Tree::DIFF, + ) + .with_note( + "The limit is actually squared, so 1000 stands for up to 1 million diffs if fuzzy rename tracking is enabled", + ); + /// The `diff.renames` key. + pub const RENAMES: Renames = Renames::new_renames("renames", &config::Tree::DIFF); +} + +impl Section for Diff { + fn name(&self) -> &str { + "diff" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::ALGORITHM, &Self::RENAME_LIMIT, &Self::RENAMES] + } +} + +/// The `diff.algorithm` key. +pub type Algorithm = keys::Any; + +/// The `diff.renames` key. +pub type Renames = keys::Any; + +mod algorithm { + use std::borrow::Cow; + + use crate::{ + bstr::BStr, + config, + config::{diff::algorithm::Error, tree::sections::diff::Algorithm}, + }; + + impl Algorithm { + /// Derive the diff algorithm identified by `name`, case-insensitively. + pub fn try_into_algorithm(&self, name: Cow<'_, BStr>) -> Result { + let algo = if name.eq_ignore_ascii_case(b"myers") || name.eq_ignore_ascii_case(b"default") { + gix_diff::blob::Algorithm::Myers + } else if name.eq_ignore_ascii_case(b"minimal") { + gix_diff::blob::Algorithm::MyersMinimal + } else if name.eq_ignore_ascii_case(b"histogram") { + gix_diff::blob::Algorithm::Histogram + } else if name.eq_ignore_ascii_case(b"patience") { + return Err(config::diff::algorithm::Error::Unimplemented { + name: name.into_owned(), + }); + } else { + return Err(Error::Unknown { + name: name.into_owned(), + }); + }; + Ok(algo) + } + } +} + +mod renames { + use std::borrow::Cow; + + use crate::{ + bstr::{BStr, ByteSlice}, + config::{ + key::GenericError, + tree::{keys, sections::diff::Renames, Section}, + }, + diff::rename::Tracking, + }; + + impl Renames { + /// Create a new instance. + pub const fn new_renames(name: &'static str, section: &'static dyn Section) -> Self { + keys::Any::new_with_validate(name, section, super::validate::Renames) + } + /// Try to convert the configuration into a valid rename tracking variant. Use `value` and if it's an error, call `value_string` + /// to try and interpret the key as string. + pub fn try_into_renames<'a>( + &'static self, + value: Result, + value_string: impl FnOnce() -> Option>, + ) -> Result { + Ok(match value { + Ok(true) => Tracking::Renames, + Ok(false) => Tracking::Disabled, + Err(err) => { + let value = value_string().ok_or_else(|| GenericError::from(self))?; + match value.as_ref().as_bytes() { + b"copy" | b"copies" => Tracking::RenamesAndCopies, + _ => return Err(GenericError::from_value(self, value.into_owned()).with_source(err)), + } + } + }) + } + } +} + +mod validate { + use std::borrow::Cow; + + use crate::{ + bstr::BStr, + config::tree::{keys, Diff}, + }; + + pub struct Algorithm; + impl keys::Validate for Algorithm { + fn validate(&self, value: &BStr) -> Result<(), Box> { + Diff::ALGORITHM.try_into_algorithm(value.into())?; + Ok(()) + } + } + + pub struct Renames; + impl keys::Validate for Renames { + fn validate(&self, value: &BStr) -> Result<(), Box> { + let boolean = gix_config::Boolean::try_from(value).map(|b| b.0); + Diff::RENAMES.try_into_renames(boolean, || Some(Cow::Borrowed(value)))?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/extensions.rs b/vendor/gix/src/config/tree/sections/extensions.rs new file mode 100644 index 000000000..77130f804 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/extensions.rs @@ -0,0 +1,59 @@ +use crate::{ + config, + config::tree::{keys, Extensions, Key, Section}, +}; + +impl Extensions { + /// The `extensions.worktreeConfig` key. + pub const WORKTREE_CONFIG: keys::Boolean = keys::Boolean::new_boolean("worktreeConfig", &config::Tree::EXTENSIONS); + /// The `extensions.objectFormat` key. + pub const OBJECT_FORMAT: ObjectFormat = + ObjectFormat::new_with_validate("objectFormat", &config::Tree::EXTENSIONS, validate::ObjectFormat).with_note( + "Support for SHA256 is prepared but not fully implemented yet. For now we abort when encountered", + ); +} + +/// The `core.checkStat` key. +pub type ObjectFormat = keys::Any; + +mod object_format { + use std::borrow::Cow; + + use crate::{bstr::BStr, config, config::tree::sections::extensions::ObjectFormat}; + + impl ObjectFormat { + pub fn try_into_object_format( + &'static self, + value: Cow<'_, BStr>, + ) -> Result { + if value.as_ref().eq_ignore_ascii_case(b"sha1") { + Ok(gix_hash::Kind::Sha1) + } else { + Err(config::key::GenericErrorWithValue::from_value(self, value.into_owned())) + } + } + } +} + +impl Section for Extensions { + fn name(&self) -> &str { + "extensions" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::OBJECT_FORMAT, &Self::WORKTREE_CONFIG] + } +} + +mod validate { + use crate::{bstr::BStr, config::tree::keys}; + + pub struct ObjectFormat; + + impl keys::Validate for ObjectFormat { + fn validate(&self, value: &BStr) -> Result<(), Box> { + super::Extensions::OBJECT_FORMAT.try_into_object_format(value.into())?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/gitoxide.rs b/vendor/gix/src/config/tree/sections/gitoxide.rs new file mode 100644 index 000000000..8c3defd0b --- /dev/null +++ b/vendor/gix/src/config/tree/sections/gitoxide.rs @@ -0,0 +1,363 @@ +use crate::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.commit` section. + pub const COMMIT: Commit = Commit; + /// The `gitoxide.committer` section. + pub const COMMITTER: Committer = Committer; + /// The `gitoxide.http` section. + pub const HTTP: Http = Http; + /// The `gitoxide.https` section. + pub const HTTPS: Https = Https; + /// The `gitoxide.objects` section. + pub const OBJECTS: Objects = Objects; + /// The `gitoxide.ssh` section. + pub const SSH: Ssh = Ssh; + /// The `gitoxide.user` section. + pub const USER: User = User; + + /// The `gitoxide.userAgent` Key. + pub const USER_AGENT: keys::Any = keys::Any::new("userAgent", &config::Tree::GITOXIDE).with_note( + "The user agent presented on the git protocol layer, serving as fallback for when no `http.userAgent` is set", + ); +} + +impl Section for Gitoxide { + fn name(&self) -> &str { + "gitoxide" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::USER_AGENT] + } + + fn sub_sections(&self) -> &[&dyn Section] { + &[ + &Self::ALLOW, + &Self::AUTHOR, + &Self::COMMIT, + &Self::COMMITTER, + &Self::HTTP, + &Self::HTTPS, + &Self::OBJECTS, + &Self::SSH, + &Self::USER, + ] + } +} + +mod subsections { + use crate::config::{ + tree::{http, keys, Gitoxide, Key, Section}, + Tree, + }; + + /// The `Http` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Http; + + impl Http { + /// The `gitoxide.http.proxy` key. + pub const PROXY: keys::String = + keys::String::new_string("proxy", &Gitoxide::HTTP).with_environment_override("http_proxy"); + /// The `gitoxide.http.allProxy` key. + pub const ALL_PROXY: keys::String = keys::String::new_string("allProxy", &Gitoxide::HTTP) + .with_environment_override("all_proxy") + .with_note("fallback environment is `ALL_PROXY`"); + /// The `gitoxide.http.verbose` key. + /// + /// If set, curl will be configured to log verbosely. + pub const VERBOSE: keys::Boolean = keys::Boolean::new_boolean("verbose", &Gitoxide::HTTP) + .with_environment_override("GIT_CURL_VERBOSE") + .with_deviation("we parse it as boolean for convenience (infallible) but git only checks the presence"); + /// The `gitoxide.http.noProxy` key. + pub const NO_PROXY: keys::String = keys::String::new_string("noProxy", &Gitoxide::HTTP) + .with_environment_override("no_proxy") + .with_note("fallback environment is `NO_PROXY`"); + /// The `gitoxide.http.connectTimeout` key. + pub const CONNECT_TIMEOUT: keys::DurationInMilliseconds = + keys::DurationInMilliseconds::new_duration("connectTimeout", &Gitoxide::HTTP).with_note( + "entirely new, and in milliseconds, to describe how long to wait until a connection attempt is aborted", + ); + /// The `gitoxide.http.sslVersionMin` key. + pub const SSL_VERSION_MIN: http::SslVersion = + http::SslVersion::new_ssl_version("sslVersionMin", &Gitoxide::HTTP).with_note( + "entirely new to set the lower bound for the allowed ssl version range. Overwrites the min bound of `http.sslVersion` if set. Min and Max must be set to become effective.", + ); + /// The `gitoxide.http.sslVersionMax` key. + pub const SSL_VERSION_MAX: http::SslVersion = + http::SslVersion::new_ssl_version("sslVersionMax", &Gitoxide::HTTP).with_note( + "entirely new to set the upper bound for the allowed ssl version range. Overwrites the max bound of `http.sslVersion` if set. Min and Max must be set to become effective.", + ); + /// The `gitoxide.http.proxyAuthMethod` key. + pub const PROXY_AUTH_METHOD: http::ProxyAuthMethod = + http::ProxyAuthMethod::new_proxy_auth_method("proxyAuthMethod", &Gitoxide::HTTP) + .with_environment_override("GIT_HTTP_PROXY_AUTHMETHOD"); + } + + impl Section for Http { + fn name(&self) -> &str { + "http" + } + + fn keys(&self) -> &[&dyn Key] { + &[ + &Self::PROXY, + &Self::ALL_PROXY, + &Self::VERBOSE, + &Self::NO_PROXY, + &Self::CONNECT_TIMEOUT, + &Self::SSL_VERSION_MIN, + &Self::SSL_VERSION_MAX, + &Self::PROXY_AUTH_METHOD, + ] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + + /// The `Https` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Https; + + impl Https { + /// The `gitoxide.https.proxy` key. + pub const PROXY: keys::String = keys::String::new_string("proxy", &Gitoxide::HTTPS) + .with_environment_override("HTTPS_PROXY") + .with_note("fallback environment variable is `https_proxy`"); + } + + impl Section for Https { + fn name(&self) -> &str { + "https" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::PROXY] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + + /// The `allow` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Allow; + + /// The `gitoxide.allow.protocolFromUser` key. + pub type ProtocolFromUser = keys::Any; + + impl Allow { + /// The `gitoxide.allow.protocolFromUser` key. + pub const PROTOCOL_FROM_USER: ProtocolFromUser = ProtocolFromUser::new_with_validate( + "protocolFromUser", + &Gitoxide::ALLOW, + super::validate::ProtocolFromUser, + ) + .with_environment_override("GIT_PROTOCOL_FROM_USER"); + } + + impl Section for Allow { + fn name(&self) -> &str { + "allow" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::PROTOCOL_FROM_USER] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + + /// The `author` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Author; + + impl Author { + /// The `gitoxide.author.nameFallback` key. + pub const NAME_FALLBACK: keys::Any = + keys::Any::new("nameFallback", &Gitoxide::AUTHOR).with_environment_override("GIT_AUTHOR_NAME"); + /// The `gitoxide.author.emailFallback` key. + pub const EMAIL_FALLBACK: keys::Any = + keys::Any::new("emailFallback", &Gitoxide::AUTHOR).with_environment_override("GIT_AUTHOR_EMAIL"); + } + + impl Section for Author { + fn name(&self) -> &str { + "author" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::NAME_FALLBACK, &Self::EMAIL_FALLBACK] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + + /// The `user` sub-section. + #[derive(Copy, Clone, Default)] + pub struct User; + + impl User { + /// The `gitoxide.user.emailFallback` key. + pub const EMAIL_FALLBACK: keys::Any = + keys::Any::new("emailFallback", &Gitoxide::USER).with_environment_override("EMAIL"); + } + + impl Section for User { + fn name(&self) -> &str { + "user" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::EMAIL_FALLBACK] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + + /// The `ssh` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Ssh; + + impl Ssh { + /// The `gitoxide.ssh.commandWithoutShellFallback` key. + pub const COMMAND_WITHOUT_SHELL_FALLBACK: keys::Executable = + keys::Executable::new_executable("commandWithoutShellFallback", &Gitoxide::SSH) + .with_environment_override("GIT_SSH") + .with_note("is always executed without shell and treated as fallback"); + } + + impl Section for Ssh { + fn name(&self) -> &str { + "ssh" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::COMMAND_WITHOUT_SHELL_FALLBACK] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + + /// The `objects` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Objects; + + impl Objects { + /// The `gitoxide.objects.cacheLimit` key. + pub const CACHE_LIMIT: keys::UnsignedInteger = + keys::UnsignedInteger::new_unsigned_integer("cacheLimit", &Gitoxide::OBJECTS) + .with_note("If unset or 0, there is no object cache") + .with_environment_override("GITOXIDE_OBJECT_CACHE_MEMORY"); + /// The `gitoxide.objects.noReplace` key. + pub const NO_REPLACE: keys::Boolean = keys::Boolean::new_boolean("noReplace", &Gitoxide::OBJECTS) + .with_environment_override("GIT_NO_REPLACE_OBJECTS"); + /// The `gitoxide.objects.replaceRefBase` key. + pub const REPLACE_REF_BASE: keys::Any = + keys::Any::new("replaceRefBase", &Gitoxide::OBJECTS).with_environment_override("GIT_REPLACE_REF_BASE"); + } + + impl Section for Objects { + fn name(&self) -> &str { + "objects" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::CACHE_LIMIT, &Self::NO_REPLACE, &Self::REPLACE_REF_BASE] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + + /// The `committer` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Committer; + + impl Committer { + /// The `gitoxide.committer.nameFallback` key. + pub const NAME_FALLBACK: keys::Any = + keys::Any::new("nameFallback", &Gitoxide::COMMITTER).with_environment_override("GIT_COMMITTER_NAME"); + /// The `gitoxide.committer.emailFallback` key. + pub const EMAIL_FALLBACK: keys::Any = + keys::Any::new("emailFallback", &Gitoxide::COMMITTER).with_environment_override("GIT_COMMITTER_EMAIL"); + } + + impl Section for Committer { + fn name(&self) -> &str { + "committer" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::NAME_FALLBACK, &Self::EMAIL_FALLBACK] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } + + /// The `commit` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Commit; + + impl Commit { + /// The `gitoxide.commit.authorDate` key. + pub const AUTHOR_DATE: keys::Time = + keys::Time::new_time("authorDate", &Gitoxide::COMMIT).with_environment_override("GIT_AUTHOR_DATE"); + /// The `gitoxide.commit.committerDate` key. + pub const COMMITTER_DATE: keys::Time = + keys::Time::new_time("committerDate", &Gitoxide::COMMIT).with_environment_override("GIT_COMMITTER_DATE"); + } + + impl Section for Commit { + fn name(&self) -> &str { + "commit" + } + + fn keys(&self) -> &[&dyn Key] { + &[] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&Tree::GITOXIDE) + } + } +} + +pub mod validate { + use std::error::Error; + + use crate::{bstr::BStr, config::tree::keys::Validate}; + + pub struct ProtocolFromUser; + impl Validate for ProtocolFromUser { + fn validate(&self, value: &BStr) -> Result<(), Box> { + if value != "1" { + return Err("GIT_PROTOCOL_FROM_USER is either unset or as the value '1'".into()); + } + Ok(()) + } + } +} + +pub use subsections::{Allow, Author, Commit, Committer, Http, Https, Objects, Ssh, User}; + +use crate::config; diff --git a/vendor/gix/src/config/tree/sections/http.rs b/vendor/gix/src/config/tree/sections/http.rs new file mode 100644 index 000000000..f45c37076 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/http.rs @@ -0,0 +1,317 @@ +use crate::{ + config, + config::tree::{keys, Http, Key, Section}, +}; + +impl Http { + /// The `http.sslVersion` key. + pub const SSL_VERSION: SslVersion = SslVersion::new_ssl_version("sslVersion", &config::Tree::HTTP) + .with_environment_override("GIT_SSL_VERSION") + .with_deviation( + "accepts the new 'default' value which means to use the curl default just like the empty string does", + ); + /// The `http.proxy` key. + pub const PROXY: keys::String = + keys::String::new_string("proxy", &config::Tree::HTTP).with_deviation("fails on strings with illformed UTF-8"); + /// The `http.proxyAuthMethod` key. + pub const PROXY_AUTH_METHOD: ProxyAuthMethod = + ProxyAuthMethod::new_proxy_auth_method("proxyAuthMethod", &config::Tree::HTTP) + .with_deviation("implemented like git, but never actually tried"); + /// The `http.version` key. + pub const VERSION: Version = Version::new_with_validate("version", &config::Tree::HTTP, validate::Version) + .with_deviation("fails on illformed UTF-8"); + /// The `http.userAgent` key. + pub const USER_AGENT: keys::String = + keys::String::new_string("userAgent", &config::Tree::HTTP).with_deviation("fails on illformed UTF-8"); + /// The `http.extraHeader` key. + pub const EXTRA_HEADER: ExtraHeader = + ExtraHeader::new_with_validate("extraHeader", &config::Tree::HTTP, validate::ExtraHeader) + .with_deviation("fails on illformed UTF-8, without leniency"); + /// The `http.followRedirects` key. + pub const FOLLOW_REDIRECTS: FollowRedirects = + FollowRedirects::new_with_validate("followRedirects", &config::Tree::HTTP, validate::FollowRedirects); + /// The `http.lowSpeedTime` key. + pub const LOW_SPEED_TIME: keys::UnsignedInteger = + keys::UnsignedInteger::new_unsigned_integer("lowSpeedTime", &config::Tree::HTTP) + .with_deviation("fails on negative values"); + /// The `http.lowSpeedLimit` key. + pub const LOW_SPEED_LIMIT: keys::UnsignedInteger = + keys::UnsignedInteger::new_unsigned_integer("lowSpeedLimit", &config::Tree::HTTP) + .with_deviation("fails on negative values"); + /// The `http.schannelUseSSLCAInfo` key. + pub const SCHANNEL_USE_SSL_CA_INFO: keys::Boolean = + keys::Boolean::new_boolean("schannelUseSSLCAInfo", &config::Tree::HTTP) + .with_deviation("only used as switch internally to turn off using the sslCAInfo, unconditionally. If unset, it has no effect, whereas in `git` it defaults to false."); + /// The `http.sslCAInfo` key. + pub const SSL_CA_INFO: keys::Path = + keys::Path::new_path("sslCAInfo", &config::Tree::HTTP).with_environment_override("GIT_SSL_CAINFO"); + /// The `http.schannelCheckRevoke` key. + pub const SCHANNEL_CHECK_REVOKE: keys::Boolean = + keys::Boolean::new_boolean("schannelCheckRevoke", &config::Tree::HTTP); +} + +impl Section for Http { + fn name(&self) -> &str { + "http" + } + + fn keys(&self) -> &[&dyn Key] { + &[ + &Self::SSL_VERSION, + &Self::PROXY, + &Self::PROXY_AUTH_METHOD, + &Self::VERSION, + &Self::USER_AGENT, + &Self::EXTRA_HEADER, + &Self::FOLLOW_REDIRECTS, + &Self::LOW_SPEED_TIME, + &Self::LOW_SPEED_LIMIT, + &Self::SCHANNEL_USE_SSL_CA_INFO, + &Self::SSL_CA_INFO, + &Self::SCHANNEL_CHECK_REVOKE, + ] + } +} + +/// The `http.followRedirects` key. +pub type FollowRedirects = keys::Any; + +/// The `http.extraHeader` key. +pub type ExtraHeader = keys::Any; + +/// The `http.sslVersion` key, as well as others of the same type. +pub type SslVersion = keys::Any; + +/// The `http.proxyAuthMethod` key, as well as others of the same type. +pub type ProxyAuthMethod = keys::Any; + +/// The `http.version` key. +pub type Version = keys::Any; + +mod key_impls { + use crate::config::tree::{ + http::{ProxyAuthMethod, SslVersion}, + keys, Section, + }; + + impl SslVersion { + pub const fn new_ssl_version(name: &'static str, section: &'static dyn Section) -> Self { + keys::Any::new_with_validate(name, section, super::validate::SslVersion) + } + } + + impl ProxyAuthMethod { + pub const fn new_proxy_auth_method(name: &'static str, section: &'static dyn Section) -> Self { + keys::Any::new_with_validate(name, section, super::validate::ProxyAuthMethod) + } + } + + #[cfg(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + ))] + impl crate::config::tree::http::FollowRedirects { + /// Convert `value` into the redirect specification, or query the same value as `boolean` + /// for additional possible input values. + /// + /// Note that `boolean` only queries the underlying key as boolean, which is a necessity to handle + /// empty booleans correctly, that is those without a value separator. + pub fn try_into_follow_redirects( + &'static self, + value: std::borrow::Cow<'_, crate::bstr::BStr>, + boolean: impl FnOnce() -> Result, gix_config::value::Error>, + ) -> Result< + crate::protocol::transport::client::http::options::FollowRedirects, + crate::config::key::GenericErrorWithValue, + > { + use crate::{bstr::ByteSlice, protocol::transport::client::http::options::FollowRedirects}; + Ok(if value.as_ref().as_bytes() == b"initial" { + FollowRedirects::Initial + } else if let Some(value) = boolean().map_err(|err| { + crate::config::key::GenericErrorWithValue::from_value(self, value.into_owned()).with_source(err) + })? { + if value { + FollowRedirects::All + } else { + FollowRedirects::None + } + } else { + FollowRedirects::Initial + }) + } + } + + impl super::ExtraHeader { + /// Convert a list of values into extra-headers, while failing entirely on illformed UTF-8. + pub fn try_into_extra_header( + &'static self, + values: Vec>, + ) -> Result, crate::config::string::Error> { + let mut out = Vec::with_capacity(values.len()); + for value in values { + if value.is_empty() { + out.clear(); + } else { + out.push(self.try_into_string(value)?); + } + } + Ok(out) + } + } + + #[cfg(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + ))] + impl super::Version { + pub fn try_into_http_version( + &'static self, + value: std::borrow::Cow<'_, crate::bstr::BStr>, + ) -> Result< + gix_protocol::transport::client::http::options::HttpVersion, + crate::config::key::GenericErrorWithValue, + > { + use gix_protocol::transport::client::http::options::HttpVersion; + + use crate::bstr::ByteSlice; + Ok(match value.as_ref().as_bytes() { + b"HTTP/1.1" => HttpVersion::V1_1, + b"HTTP/2" => HttpVersion::V2, + _ => { + return Err(crate::config::key::GenericErrorWithValue::from_value( + self, + value.into_owned(), + )) + } + }) + } + } + + #[cfg(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + ))] + impl ProxyAuthMethod { + pub fn try_into_proxy_auth_method( + &'static self, + value: std::borrow::Cow<'_, crate::bstr::BStr>, + ) -> Result< + gix_protocol::transport::client::http::options::ProxyAuthMethod, + crate::config::key::GenericErrorWithValue, + > { + use gix_protocol::transport::client::http::options::ProxyAuthMethod; + + use crate::bstr::ByteSlice; + Ok(match value.as_ref().as_bytes() { + b"anyauth" => ProxyAuthMethod::AnyAuth, + b"basic" => ProxyAuthMethod::Basic, + b"digest" => ProxyAuthMethod::Digest, + b"negotiate" => ProxyAuthMethod::Negotiate, + b"ntlm" => ProxyAuthMethod::Ntlm, + _ => { + return Err(crate::config::key::GenericErrorWithValue::from_value( + self, + value.into_owned(), + )) + } + }) + } + } + + #[cfg(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + ))] + impl SslVersion { + pub fn try_into_ssl_version( + &'static self, + value: std::borrow::Cow<'_, crate::bstr::BStr>, + ) -> Result + { + use gix_protocol::transport::client::http::options::SslVersion::*; + + use crate::bstr::ByteSlice; + Ok(match value.as_ref().as_bytes() { + b"default" | b"" => Default, + b"tlsv1" => TlsV1, + b"sslv2" => SslV2, + b"sslv3" => SslV3, + b"tlsv1.0" => TlsV1_0, + b"tlsv1.1" => TlsV1_1, + b"tlsv1.2" => TlsV1_2, + b"tlsv1.3" => TlsV1_3, + _ => return Err(crate::config::ssl_version::Error::from_value(self, value.into_owned())), + }) + } + } +} + +pub mod validate { + use std::error::Error; + + use crate::{ + bstr::{BStr, ByteSlice}, + config::tree::keys::Validate, + }; + + pub struct SslVersion; + impl Validate for SslVersion { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + #[cfg(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + ))] + super::Http::SSL_VERSION.try_into_ssl_version(std::borrow::Cow::Borrowed(_value))?; + + Ok(()) + } + } + + pub struct ProxyAuthMethod; + impl Validate for ProxyAuthMethod { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + #[cfg(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + ))] + super::Http::PROXY_AUTH_METHOD.try_into_proxy_auth_method(std::borrow::Cow::Borrowed(_value))?; + + Ok(()) + } + } + + pub struct Version; + impl Validate for Version { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + #[cfg(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + ))] + super::Http::VERSION.try_into_http_version(std::borrow::Cow::Borrowed(_value))?; + + Ok(()) + } + } + + pub struct ExtraHeader; + impl Validate for ExtraHeader { + fn validate(&self, value: &BStr) -> Result<(), Box> { + value.to_str()?; + Ok(()) + } + } + + pub struct FollowRedirects; + impl Validate for FollowRedirects { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + #[cfg(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + ))] + super::Http::FOLLOW_REDIRECTS.try_into_follow_redirects(std::borrow::Cow::Borrowed(_value), || { + gix_config::Boolean::try_from(_value).map(|b| Some(b.0)) + })?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/init.rs b/vendor/gix/src/config/tree/sections/init.rs new file mode 100644 index 000000000..de42d3b62 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/init.rs @@ -0,0 +1,20 @@ +use crate::{ + config, + config::tree::{keys, Init, Key, Section}, +}; + +impl Init { + /// The `init.defaultBranch` key. + pub const DEFAULT_BRANCH: keys::Any = keys::Any::new("defaultBranch", &config::Tree::INIT) + .with_deviation("If not set, we use `main` instead of `master`"); +} + +impl Section for Init { + fn name(&self) -> &str { + "init" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::DEFAULT_BRANCH] + } +} diff --git a/vendor/gix/src/config/tree/sections/mod.rs b/vendor/gix/src/config/tree/sections/mod.rs new file mode 100644 index 000000000..fb9b50786 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/mod.rs @@ -0,0 +1,96 @@ +#![allow(missing_docs)] + +/// The `author` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Author; +mod author; + +/// The `branch` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Branch; +pub mod branch; + +/// The `checkout` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Checkout; +pub mod checkout; + +/// The `clone` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Clone; +mod clone; + +/// The `committer` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Committer; +mod committer; + +/// The `core` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Core; +pub mod core; + +/// The `credential` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Credential; +pub mod credential; + +/// The `diff` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Diff; +pub mod diff; + +/// The `extension` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Extensions; +pub mod extensions; + +/// The `gitoxide` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Gitoxide; +pub mod gitoxide; + +/// The `http` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Http; +pub mod http; + +/// The `init` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Init; +mod init; + +/// The `pack` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Pack; +pub mod pack; + +/// The `protocol` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Protocol; +pub mod protocol; + +/// The `remote` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Remote; +pub mod remote; + +/// The `safe` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Safe; +mod safe; + +/// The `ssh` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Ssh; +pub mod ssh; + +/// The `user` top-level section. +#[derive(Copy, Clone, Default)] +pub struct User; +mod user; + +/// The `url` top-level section. +#[derive(Copy, Clone, Default)] +pub struct Url; +mod url; diff --git a/vendor/gix/src/config/tree/sections/pack.rs b/vendor/gix/src/config/tree/sections/pack.rs new file mode 100644 index 000000000..941817e5b --- /dev/null +++ b/vendor/gix/src/config/tree/sections/pack.rs @@ -0,0 +1,64 @@ +use crate::{ + config, + config::tree::{keys, Key, Pack, Section}, +}; + +impl Pack { + /// The `pack.threads` key. + pub const THREADS: keys::UnsignedInteger = + keys::UnsignedInteger::new_unsigned_integer("threads", &config::Tree::PACK) + .with_deviation("Leaving this key unspecified uses all available cores, instead of 1"); + + /// The `pack.indexVersion` key. + pub const INDEX_VERSION: IndexVersion = + IndexVersion::new_with_validate("indexVersion", &config::Tree::PACK, validate::IndexVersion); +} + +/// The `pack.indexVersion` key. +pub type IndexVersion = keys::Any; + +mod index_version { + use crate::{config, config::tree::sections::pack::IndexVersion}; + + impl IndexVersion { + /// Try to interpret an integer value as index version. + pub fn try_into_index_version( + &'static self, + value: Result, + ) -> Result { + let value = value.map_err(|err| config::key::GenericError::from(self).with_source(err))?; + Ok(match value { + 1 => gix_pack::index::Version::V1, + 2 => gix_pack::index::Version::V2, + _ => return Err(config::key::GenericError::from(self)), + }) + } + } +} + +impl Section for Pack { + fn name(&self) -> &str { + "pack" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::THREADS, &Self::INDEX_VERSION] + } +} + +mod validate { + use crate::{bstr::BStr, config::tree::keys}; + + pub struct IndexVersion; + impl keys::Validate for IndexVersion { + fn validate(&self, value: &BStr) -> Result<(), Box> { + super::Pack::INDEX_VERSION.try_into_index_version(gix_config::Integer::try_from(value).and_then( + |int| { + int.to_decimal() + .ok_or_else(|| gix_config::value::Error::new("integer out of range", value)) + }, + ))?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/protocol.rs b/vendor/gix/src/config/tree/sections/protocol.rs new file mode 100644 index 000000000..58e907b0f --- /dev/null +++ b/vendor/gix/src/config/tree/sections/protocol.rs @@ -0,0 +1,85 @@ +use crate::{ + config, + config::tree::{keys, Key, Protocol, Section}, +}; + +impl Protocol { + /// The `protocol.allow` key. + pub const ALLOW: Allow = Allow::new_with_validate("allow", &config::Tree::PROTOCOL, validate::Allow); + + /// The `protocol.` subsection + pub const NAME_PARAMETER: NameParameter = NameParameter; +} + +/// The `protocol.allow` key type. +pub type Allow = keys::Any; + +#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] +mod allow { + use std::borrow::Cow; + + use crate::{bstr::BStr, config, config::tree::protocol::Allow, remote::url::scheme_permission}; + + impl Allow { + /// Convert `value` into its respective `Allow` variant, possibly informing about the `scheme` we are looking at in the error. + pub fn try_into_allow( + &'static self, + value: Cow<'_, BStr>, + scheme: Option<&str>, + ) -> Result { + scheme_permission::Allow::try_from(value).map_err(|value| config::protocol::allow::Error { + value, + scheme: scheme.map(ToOwned::to_owned), + }) + } + } +} + +/// The `protocol.` parameter section. +pub struct NameParameter; + +impl NameParameter { + /// The `credential..helper` key. + pub const ALLOW: Allow = Allow::new_with_validate("allow", &Protocol::NAME_PARAMETER, validate::Allow); +} + +impl Section for NameParameter { + fn name(&self) -> &str { + "" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::ALLOW] + } + + fn parent(&self) -> Option<&dyn Section> { + Some(&config::Tree::PROTOCOL) + } +} + +impl Section for Protocol { + fn name(&self) -> &str { + "protocol" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::ALLOW] + } + + fn sub_sections(&self) -> &[&dyn Section] { + &[&Self::NAME_PARAMETER] + } +} + +mod validate { + use crate::{bstr::BStr, config::tree::keys}; + + pub struct Allow; + impl keys::Validate for Allow { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] + super::Protocol::ALLOW.try_into_allow(std::borrow::Cow::Borrowed(_value), None)?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/remote.rs b/vendor/gix/src/config/tree/sections/remote.rs new file mode 100644 index 000000000..b242c9c14 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/remote.rs @@ -0,0 +1,101 @@ +use crate::{ + config, + config::tree::{http, keys, Key, Remote, Section, SubSectionRequirement}, +}; + +const NAME_PARAMETER: Option = Some(SubSectionRequirement::Parameter("name")); + +impl Remote { + /// The `remote.pushDefault` key + pub const PUSH_DEFAULT: keys::RemoteName = keys::RemoteName::new_remote_name("pushDefault", &config::Tree::REMOTE); + /// The `remote..tagOpt` key + pub const TAG_OPT: TagOpt = TagOpt::new_with_validate("tagOpt", &config::Tree::REMOTE, validate::TagOpt) + .with_subsection_requirement(Some(SubSectionRequirement::Parameter("name"))); + /// The `remote..url` key + pub const URL: keys::Url = + keys::Url::new_url("url", &config::Tree::REMOTE).with_subsection_requirement(NAME_PARAMETER); + /// The `remote..pushUrl` key + pub const PUSH_URL: keys::Url = + keys::Url::new_url("pushUrl", &config::Tree::REMOTE).with_subsection_requirement(NAME_PARAMETER); + /// The `remote..fetch` key + pub const FETCH: keys::FetchRefSpec = keys::FetchRefSpec::new_fetch_refspec("fetch", &config::Tree::REMOTE) + .with_subsection_requirement(NAME_PARAMETER); + /// The `remote..push` key + pub const PUSH: keys::PushRefSpec = + keys::PushRefSpec::new_push_refspec("push", &config::Tree::REMOTE).with_subsection_requirement(NAME_PARAMETER); + /// The `remote..proxy` key + pub const PROXY: keys::String = + keys::String::new_string("proxy", &config::Tree::REMOTE).with_subsection_requirement(NAME_PARAMETER); + /// The `remote..proxyAuthMethod` key. + pub const PROXY_AUTH_METHOD: http::ProxyAuthMethod = + http::ProxyAuthMethod::new_proxy_auth_method("proxyAuthMethod", &config::Tree::REMOTE) + .with_subsection_requirement(NAME_PARAMETER) + .with_deviation("implemented like git, but never actually tried"); +} + +impl Section for Remote { + fn name(&self) -> &str { + "remote" + } + + fn keys(&self) -> &[&dyn Key] { + &[ + &Self::PUSH_DEFAULT, + &Self::TAG_OPT, + &Self::URL, + &Self::PUSH_URL, + &Self::FETCH, + &Self::PUSH, + &Self::PROXY, + &Self::PROXY_AUTH_METHOD, + ] + } +} + +/// The `remote..tagOpt` key type. +pub type TagOpt = keys::Any; + +mod tag_opts { + use std::borrow::Cow; + + use crate::{ + bstr::{BStr, ByteSlice}, + config, + config::tree::remote::TagOpt, + remote, + }; + + impl TagOpt { + /// Try to interpret `value` as tag option. + /// + /// # Note + /// + /// It's heavily biased towards the git command-line unfortunately, and the only + /// value of its kind. Maybe in future more values will be supported which are less + /// about passing them to a sub-process. + pub fn try_into_tag_opt( + &'static self, + value: Cow<'_, BStr>, + ) -> Result { + Ok(match value.as_ref().as_bytes() { + b"--tags" => remote::fetch::Tags::All, + b"--no-tags" => remote::fetch::Tags::None, + _ => return Err(config::key::GenericErrorWithValue::from_value(self, value.into_owned())), + }) + } + } +} + +pub mod validate { + use std::{borrow::Cow, error::Error}; + + use crate::{bstr::BStr, config::tree::keys::Validate}; + + pub struct TagOpt; + impl Validate for TagOpt { + fn validate(&self, value: &BStr) -> Result<(), Box> { + super::Remote::TAG_OPT.try_into_tag_opt(Cow::Borrowed(value))?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/safe.rs b/vendor/gix/src/config/tree/sections/safe.rs new file mode 100644 index 000000000..e76d28888 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/safe.rs @@ -0,0 +1,27 @@ +use crate::{ + config, + config::tree::{keys, Key, Safe, Section}, +}; + +impl Safe { + /// The `safe.directory` key + pub const DIRECTORY: keys::Any = keys::Any::new("directory", &config::Tree::SAFE); +} + +impl Safe { + /// Implements the directory filter to trust only global and system files, for use with `safe.directory`. + pub fn directory_filter(meta: &gix_config::file::Metadata) -> bool { + let kind = meta.source.kind(); + kind == gix_config::source::Kind::System || kind == gix_config::source::Kind::Global + } +} + +impl Section for Safe { + fn name(&self) -> &str { + "safe" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::DIRECTORY] + } +} diff --git a/vendor/gix/src/config/tree/sections/ssh.rs b/vendor/gix/src/config/tree/sections/ssh.rs new file mode 100644 index 000000000..600ee663b --- /dev/null +++ b/vendor/gix/src/config/tree/sections/ssh.rs @@ -0,0 +1,65 @@ +use crate::{ + config, + config::tree::{keys, Key, Section, Ssh}, +}; + +impl Ssh { + /// The `ssh.variant` key + pub const VARIANT: Variant = Variant::new_with_validate("variant", &config::Tree::SSH, validate::Variant) + .with_environment_override("GIT_SSH_VARIANT") + .with_deviation("We error if a variant is chosen that we don't know, as opposed to defaulting to 'ssh'"); +} + +/// The `ssh.variant` key. +pub type Variant = keys::Any; + +#[cfg(feature = "blocking-network-client")] +mod variant { + use std::borrow::Cow; + + use crate::{bstr::BStr, config, config::tree::ssh::Variant}; + + impl Variant { + pub fn try_into_variant( + &'static self, + value: Cow<'_, BStr>, + ) -> Result, config::key::GenericErrorWithValue> + { + use gix_protocol::transport::client::ssh::ProgramKind; + + use crate::bstr::ByteSlice; + Ok(Some(match value.as_ref().as_bytes() { + b"auto" => return Ok(None), + b"ssh" => ProgramKind::Ssh, + b"plink" => ProgramKind::Plink, + b"putty" => ProgramKind::Putty, + b"tortoiseplink" => ProgramKind::TortoisePlink, + b"simple" => ProgramKind::Simple, + _ => return Err(config::key::GenericErrorWithValue::from_value(self, value.into_owned())), + })) + } + } +} + +impl Section for Ssh { + fn name(&self) -> &str { + "ssh" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::VARIANT] + } +} + +mod validate { + use crate::{bstr::BStr, config::tree::keys}; + + pub struct Variant; + impl keys::Validate for Variant { + fn validate(&self, _value: &BStr) -> Result<(), Box> { + #[cfg(feature = "blocking-network-client")] + super::Ssh::VARIANT.try_into_variant(_value.into())?; + Ok(()) + } + } +} diff --git a/vendor/gix/src/config/tree/sections/url.rs b/vendor/gix/src/config/tree/sections/url.rs new file mode 100644 index 000000000..6a9c0bfdb --- /dev/null +++ b/vendor/gix/src/config/tree/sections/url.rs @@ -0,0 +1,25 @@ +use crate::{ + config, + config::tree::{keys, Key, Section, SubSectionRequirement, Url}, +}; + +const BASE_PARAMETER: Option = Some(SubSectionRequirement::Parameter("base")); + +impl Url { + /// The `url..insteadOf` key + pub const INSTEAD_OF: keys::Any = + keys::Any::new("insteadOf", &config::Tree::URL).with_subsection_requirement(BASE_PARAMETER); + /// The `url..pushInsteadOf` key + pub const PUSH_INSTEAD_OF: keys::Any = + keys::Any::new("pushInsteadOf", &config::Tree::URL).with_subsection_requirement(BASE_PARAMETER); +} + +impl Section for Url { + fn name(&self) -> &str { + "url" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::INSTEAD_OF, &Self::PUSH_INSTEAD_OF] + } +} diff --git a/vendor/gix/src/config/tree/sections/user.rs b/vendor/gix/src/config/tree/sections/user.rs new file mode 100644 index 000000000..d1f4f7102 --- /dev/null +++ b/vendor/gix/src/config/tree/sections/user.rs @@ -0,0 +1,22 @@ +use crate::{ + config, + config::tree::{gitoxide, keys, Key, Section, User}, +}; + +impl User { + /// The `user.name` key + pub const NAME: keys::Any = keys::Any::new("name", &config::Tree::USER); + /// The `user.email` key + pub const EMAIL: keys::Any = + keys::Any::new("email", &config::Tree::USER).with_fallback(&gitoxide::User::EMAIL_FALLBACK); +} + +impl Section for User { + fn name(&self) -> &str { + "user" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::NAME, &Self::EMAIL] + } +} diff --git a/vendor/gix/src/config/tree/traits.rs b/vendor/gix/src/config/tree/traits.rs new file mode 100644 index 000000000..7cfd7aac4 --- /dev/null +++ b/vendor/gix/src/config/tree/traits.rs @@ -0,0 +1,199 @@ +use crate::{ + bstr::{BStr, BString, ByteVec}, + config::tree::key::validate_assignment, +}; + +/// Provide information about a configuration section. +pub trait Section { + /// The section name, like `remote` in `remote.origin.url`. + fn name(&self) -> &str; + /// The keys directly underneath it for carrying configuration values. + fn keys(&self) -> &[&dyn Key]; + /// The list of sub-section names, which may be empty if there are no statically known sub-sections. + fn sub_sections(&self) -> &[&dyn Section] { + &[] + } + /// The parent section if this is a statically known sub-section. + fn parent(&self) -> Option<&dyn Section> { + None + } +} + +/// Determine how subsections may be used with a given key, suitable for obtaining the full name for use in assignments. +#[derive(Debug, Copy, Clone)] +pub enum SubSectionRequirement { + /// Subsections must not be used, this key can only be below a section. + Never, + /// The sub-section is used as parameter with the given name. + Parameter(&'static str), +} + +/// A way to link a key with other resources. +#[derive(Debug, Copy, Clone)] +pub enum Link { + /// The environment variable of the given name will override the value of this key. + EnvironmentOverride(&'static str), + /// This config key is used as fallback if this key isn't set. + FallbackKey(&'static dyn Key), +} + +/// A note attached to a key. +#[derive(Debug, Copy, Clone)] +pub enum Note { + /// A piece of information related to a key to help the user. + Informative(&'static str), + /// This key works differently than is described by git, explaining the deviation further. + Deviation(&'static str), +} + +/// A leaf-level entry in the git configuration, like `url` in `remote.origin.url`. +pub trait Key: std::fmt::Debug { + /// The key's name, like `url` in `remote.origin.url`. + fn name(&self) -> &str; + /// See if `value` is allowed as value of this key, or return a descriptive error if it is not. + fn validate(&self, value: &BStr) -> Result<(), crate::config::tree::key::validate::Error>; + /// The section containing this key. Git configuration has no free-standing keys, they are always underneath a section. + fn section(&self) -> &dyn Section; + /// The return value encodes three possible states to indicate subsection requirements + /// * `None` = subsections may or may not be used, the most flexible setting. + /// * `Some([Requirement][SubSectionRequirement])` = subsections must or must not be used, depending on the value + fn subsection_requirement(&self) -> Option<&SubSectionRequirement> { + Some(&SubSectionRequirement::Never) + } + /// Return the link to other resources, if available. + fn link(&self) -> Option<&Link> { + None + } + /// Return a note about this key, if available. + fn note(&self) -> Option<&Note> { + None + } + + /// Return the name of an environment variable that would override this value (after following links until one is found). + fn environment_override(&self) -> Option<&str> { + let mut cursor = self.link()?; + loop { + match cursor { + Link::EnvironmentOverride(name) => return Some(name), + Link::FallbackKey(next) => { + cursor = next.link()?; + } + } + } + } + + /// Return the environment override that must be set on this key. + /// # Panics + /// If no environment variable is set + fn the_environment_override(&self) -> &str { + self.environment_override() + .expect("BUG: environment override must be set") + } + /// Produce a name that describes how the name is composed. This is `core.bare` for statically known keys, or `branch..key` + /// for complex ones. + fn logical_name(&self) -> String { + let section = self.section(); + let mut buf = String::new(); + let parameter = if let Some(parent) = section.parent() { + buf.push_str(parent.name()); + buf.push('.'); + None + } else { + self.subsection_requirement().and_then(|requirement| match requirement { + SubSectionRequirement::Parameter(name) => Some(name), + SubSectionRequirement::Never => None, + }) + }; + buf.push_str(section.name()); + buf.push('.'); + if let Some(parameter) = parameter { + buf.push('<'); + buf.push_str(parameter); + buf.push('>'); + buf.push('.'); + } + buf.push_str(self.name()); + buf + } + + /// The full name of the key for use in configuration overrides, like `core.bare`, or `remote..url` if `subsection` is + /// not `None`. + /// May fail if this key needs a subsection, or may not have a subsection. + fn full_name(&self, subsection: Option<&BStr>) -> Result { + let section = self.section(); + let mut buf = BString::default(); + let subsection = match self.subsection_requirement() { + None => subsection, + Some(requirement) => match (requirement, subsection) { + (SubSectionRequirement::Never, Some(_)) => { + return Err(format!( + "The key named '{}' cannot be used with non-static subsections.", + self.logical_name() + )); + } + (SubSectionRequirement::Parameter(_), None) => { + return Err(format!( + "The key named '{}' cannot be used without subsections.", + self.logical_name() + )) + } + _ => subsection, + }, + }; + + if let Some(parent) = section.parent() { + buf.push_str(parent.name()); + buf.push(b'.'); + } + buf.push_str(section.name()); + buf.push(b'.'); + if let Some(subsection) = subsection { + debug_assert!( + section.parent().is_none(), + "BUG: sections with parameterized sub-sections must be top-level sections" + ); + buf.push_str(subsection); + buf.push(b'.'); + } + buf.push_str(self.name()); + Ok(buf) + } + + /// Return an assignment with the keys full name to `value`, suitable for [configuration overrides][crate::open::Options::config_overrides()]. + /// Note that this will fail if the key requires a subsection name. + fn validated_assignment(&self, value: &BStr) -> Result { + self.validate(value)?; + let mut key = self + .full_name(None) + .map_err(|message| validate_assignment::Error::Name { message })?; + key.push(b'='); + key.push_str(value); + Ok(key) + } + + /// Return an assignment with the keys full name to `value`, suitable for [configuration overrides][crate::open::Options::config_overrides()]. + /// Note that this will fail if the key requires a subsection name. + fn validated_assignment_fmt( + &self, + value: &dyn std::fmt::Display, + ) -> Result { + let value = value.to_string(); + self.validated_assignment(value.as_str().into()) + } + + /// Return an assignment to `value` with the keys full name within `subsection`, suitable for [configuration overrides][crate::open::Options::config_overrides()]. + /// Note that this is only valid if this key supports parameterized sub-sections, or else an error is returned. + fn validated_assignment_with_subsection( + &self, + value: &BStr, + subsection: &BStr, + ) -> Result { + self.validate(value)?; + let mut key = self + .full_name(Some(subsection)) + .map_err(|message| validate_assignment::Error::Name { message })?; + key.push(b'='); + key.push_str(value); + Ok(key) + } +} -- cgit v1.2.3