summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/config/snapshot
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix/src/config/snapshot
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-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.rs76
-rw-r--r--vendor/gix/src/config/snapshot/access.rs143
-rw-r--r--vendor/gix/src/config/snapshot/credential_helpers.rs183
-rw-r--r--vendor/gix/src/config/snapshot/mod.rs5
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;