diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix/src/config/snapshot | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip |
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix/src/config/snapshot')
-rw-r--r-- | vendor/gix/src/config/snapshot/_impls.rs | 76 | ||||
-rw-r--r-- | vendor/gix/src/config/snapshot/access.rs | 143 | ||||
-rw-r--r-- | vendor/gix/src/config/snapshot/credential_helpers.rs | 183 | ||||
-rw-r--r-- | vendor/gix/src/config/snapshot/mod.rs | 5 |
4 files changed, 407 insertions, 0 deletions
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<bool> { + 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<Result<bool, gix_config::value::Error>> { + 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<i64> { + 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<Result<i64, gix_config::value::Error>> { + 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<Cow<'_, BStr>> { + 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<Result<Cow<'_, std::path::Path>, 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<Item = impl AsRef<BStr>>, + 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<CommitAutoRollback<'repo>, 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; |