diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 18:31:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 18:31:44 +0000 |
commit | c23a457e72abe608715ac76f076f47dc42af07a5 (patch) | |
tree | 2772049aaf84b5c9d0ed12ec8d86812f7a7904b6 /vendor/gix-submodule/src | |
parent | Releasing progress-linux version 1.73.0+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-c23a457e72abe608715ac76f076f47dc42af07a5.tar.xz rustc-c23a457e72abe608715ac76f076f47dc42af07a5.zip |
Merging upstream version 1.74.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-submodule/src')
-rw-r--r-- | vendor/gix-submodule/src/access.rs | 248 | ||||
-rw-r--r-- | vendor/gix-submodule/src/config.rs | 216 | ||||
-rw-r--r-- | vendor/gix-submodule/src/is_active_platform.rs | 49 | ||||
-rw-r--r-- | vendor/gix-submodule/src/lib.rs | 144 |
4 files changed, 657 insertions, 0 deletions
diff --git a/vendor/gix-submodule/src/access.rs b/vendor/gix-submodule/src/access.rs new file mode 100644 index 000000000..4def2f42f --- /dev/null +++ b/vendor/gix-submodule/src/access.rs @@ -0,0 +1,248 @@ +use std::{borrow::Cow, collections::HashSet, path::Path}; + +use bstr::BStr; + +use crate::{ + config, + config::{Branch, FetchRecurse, Ignore, Update}, + File, IsActivePlatform, +}; + +/// High-Level Access +/// +/// Note that all methods perform validation of the requested value and report issues right away. +/// If a bypass is needed, use [`config()`](File::config()) for direct access. +impl File { + /// Return the underlying configuration file. + /// + /// Note that it might have been merged with values from another configuration file and may + /// thus not be accurately reflecting that state of a `.gitmodules` file anymore. + pub fn config(&self) -> &gix_config::File<'static> { + &self.config + } + + /// Return the path at which the `.gitmodules` file lives, if it is known. + pub fn config_path(&self) -> Option<&Path> { + self.config.sections().filter_map(|s| s.meta().path.as_deref()).next() + } + + /// Return the unvalidated names of the submodules for which configuration is present. + /// + /// Note that these exact names have to be used for querying submodule values. + pub fn names(&self) -> impl Iterator<Item = &BStr> { + let mut seen = HashSet::<&BStr>::default(); + self.config + .sections_by_name("submodule") + .into_iter() + .flatten() + .filter_map(move |s| { + s.header() + .subsection_name() + .filter(|_| s.meta().source == crate::init::META_MARKER) + .filter(|name| seen.insert(*name)) + }) + } + + /// Similar to [Self::is_active_platform()], but automatically applies it to each name to learn if a submodule is active or not. + pub fn names_and_active_state<'a>( + &'a self, + config: &'a gix_config::File<'static>, + defaults: gix_pathspec::Defaults, + attributes: &'a mut (dyn FnMut( + &BStr, + gix_pathspec::attributes::glob::pattern::Case, + bool, + &mut gix_pathspec::attributes::search::Outcome, + ) -> bool + + 'a), + ) -> Result< + impl Iterator<Item = (&BStr, Result<bool, gix_config::value::Error>)> + 'a, + crate::is_active_platform::Error, + > { + let mut platform = self.is_active_platform(config, defaults)?; + let iter = self + .names() + .map(move |name| (name, platform.is_active(config, name, attributes))); + Ok(iter) + } + + /// Return a platform which allows to check if a submodule name is active or inactive. + /// Use `defaults` for parsing the pathspecs used to later match on names via `submodule.active` configuration retrieved from `config`. + /// + /// All `submodule.active` pathspecs are considered to be top-level specs and match the name of submodules, which are active + /// on inclusive match. + /// The full algorithm is described as [hierarchy of rules](https://git-scm.com/docs/gitsubmodules#_active_submodules). + pub fn is_active_platform( + &self, + config: &gix_config::File<'_>, + defaults: gix_pathspec::Defaults, + ) -> Result<IsActivePlatform, crate::is_active_platform::Error> { + let search = config + .strings_by_key("submodule.active") + .map(|patterns| -> Result<_, crate::is_active_platform::Error> { + let patterns = patterns + .into_iter() + .map(|pattern| gix_pathspec::parse(&pattern, defaults)) + .collect::<Result<Vec<_>, _>>()?; + Ok(gix_pathspec::Search::from_specs( + patterns, + None, + std::path::Path::new(""), + )?) + }) + .transpose()?; + Ok(IsActivePlatform { search }) + } + + /// Given the `relative_path` (as seen from the root of the worktree) of a submodule with possibly platform-specific + /// component separators, find the submodule's name associated with this path, or `None` if none was found. + /// + /// Note that this does a linear search and compares `relative_path` in a normalized form to the same form of the path + /// associated with the submodule. + pub fn name_by_path(&self, relative_path: &BStr) -> Option<&BStr> { + self.names() + .filter_map(|n| self.path(n).ok().map(|p| (n, p))) + .find_map(|(n, p)| (p == relative_path).then_some(n)) + } +} + +/// Per-Submodule Access +impl File { + /// Return the path relative to the root directory of the working tree at which the submodule is expected to be checked out. + /// It's an error if the path doesn't exist as it's the only way to associate a path in the index with additional submodule + /// information, like the URL to fetch from. + /// + /// ### Deviation + /// + /// Git currently allows absolute paths to be used when adding submodules, but fails later as it can't find the submodule by + /// relative path anymore. Let's play it safe here. + pub fn path(&self, name: &BStr) -> Result<Cow<'_, BStr>, config::path::Error> { + let path_bstr = + self.config + .string("submodule", Some(name), "path") + .ok_or_else(|| config::path::Error::Missing { + submodule: name.to_owned(), + })?; + if path_bstr.is_empty() { + return Err(config::path::Error::Missing { + submodule: name.to_owned(), + }); + } + let path = gix_path::from_bstr(path_bstr.as_ref()); + if path.is_absolute() { + return Err(config::path::Error::Absolute { + submodule: name.to_owned(), + actual: path_bstr.into_owned(), + }); + } + if gix_path::normalize(path, "".as_ref()).is_none() { + return Err(config::path::Error::OutsideOfWorktree { + submodule: name.to_owned(), + actual: path_bstr.into_owned(), + }); + } + Ok(path_bstr) + } + + /// Retrieve the `url` field of the submodule named `name`. It's an error if it doesn't exist or is empty. + pub fn url(&self, name: &BStr) -> Result<gix_url::Url, config::url::Error> { + let url = self + .config + .string("submodule", Some(name), "url") + .ok_or_else(|| config::url::Error::Missing { + submodule: name.to_owned(), + })?; + + if url.is_empty() { + return Err(config::url::Error::Missing { + submodule: name.to_owned(), + }); + } + gix_url::Url::from_bytes(url.as_ref()).map_err(|err| config::url::Error::Parse { + submodule: name.to_owned(), + source: err, + }) + } + + /// Retrieve the `update` field of the submodule named `name`, if present. + pub fn update(&self, name: &BStr) -> Result<Option<Update>, config::update::Error> { + let value: Update = match self.config.string("submodule", Some(name), "update") { + Some(v) => v.as_ref().try_into().map_err(|()| config::update::Error::Invalid { + submodule: name.to_owned(), + actual: v.into_owned(), + })?, + None => return Ok(None), + }; + + if let Update::Command(cmd) = &value { + let ours = self.config.meta(); + let has_value_from_foreign_section = self + .config + .sections_by_name("submodule") + .into_iter() + .flatten() + .any(|s| (s.header().subsection_name() == Some(name) && s.meta() as *const _ != ours as *const _)); + if !has_value_from_foreign_section { + return Err(config::update::Error::CommandForbiddenInModulesConfiguration { + submodule: name.to_owned(), + actual: cmd.to_owned(), + }); + } + } + Ok(Some(value)) + } + + /// Retrieve the `branch` field of the submodule named `name`, or `None` if unset. + /// + /// Note that `Default` is implemented for [`Branch`]. + pub fn branch(&self, name: &BStr) -> Result<Option<Branch>, config::branch::Error> { + let branch = match self.config.string("submodule", Some(name), "branch") { + Some(v) => v, + None => return Ok(None), + }; + + Branch::try_from(branch.as_ref()) + .map(Some) + .map_err(|err| config::branch::Error { + submodule: name.to_owned(), + actual: branch.into_owned(), + source: err, + }) + } + + /// Retrieve the `fetchRecurseSubmodules` field of the submodule named `name`, or `None` if unset. + /// + /// Note that if it's unset, it should be retrieved from `fetch.recurseSubmodules` in the configuration. + pub fn fetch_recurse(&self, name: &BStr) -> Result<Option<FetchRecurse>, config::Error> { + self.config + .boolean("submodule", Some(name), "fetchRecurseSubmodules") + .map(FetchRecurse::new) + .transpose() + .map_err(|value| config::Error { + field: "fetchRecurseSubmodules", + submodule: name.to_owned(), + actual: value, + }) + } + + /// Retrieve the `ignore` field of the submodule named `name`, or `None` if unset. + pub fn ignore(&self, name: &BStr) -> Result<Option<Ignore>, config::Error> { + self.config + .string("submodule", Some(name), "ignore") + .map(|value| { + Ignore::try_from(value.as_ref()).map_err(|()| config::Error { + field: "ignore", + submodule: name.to_owned(), + actual: value.into_owned(), + }) + }) + .transpose() + } + + /// Retrieve the `shallow` field of the submodule named `name`, or `None` if unset. + /// + /// If `true`, the submodule will be checked out with `depth = 1`. If unset, `false` is assumed. + pub fn shallow(&self, name: &BStr) -> Result<Option<bool>, gix_config::value::Error> { + self.config.boolean("submodule", Some(name), "shallow").transpose() + } +} diff --git a/vendor/gix-submodule/src/config.rs b/vendor/gix-submodule/src/config.rs new file mode 100644 index 000000000..202696691 --- /dev/null +++ b/vendor/gix-submodule/src/config.rs @@ -0,0 +1,216 @@ +use bstr::{BStr, BString, ByteSlice}; + +/// Determine how the submodule participates in `git status` queries. This setting also affects `git diff`. +#[derive(Default, Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum Ignore { + /// Submodule changes won't be considered at all, which is the fastest option. + /// + /// Note that changes to the submodule hash in the superproject will still be observable. + All, + /// Ignore any changes to the submodule working tree, only show committed differences between the `HEAD` of the submodule + /// compared to the recorded commit in the superproject. + Dirty, + /// Only ignore untracked files in the submodule, but show modifications to the submodule working tree as well as differences + /// between the recorded commit in the superproject and the checked-out commit in the submodule. + Untracked, + /// No modifications to the submodule are ignored, which shows untracked files, modified files in the submodule worktree as well as + /// differences between the recorded commit in the superproject and the checked-out commit in the submodule. + #[default] + None, +} + +impl TryFrom<&BStr> for Ignore { + type Error = (); + + fn try_from(value: &BStr) -> Result<Self, Self::Error> { + Ok(match value.as_bytes() { + b"all" => Ignore::All, + b"dirty" => Ignore::Dirty, + b"untracked" => Ignore::Untracked, + b"none" => Ignore::None, + _ => return Err(()), + }) + } +} + +/// Determine how to recurse into this module from the superproject when fetching. +/// +/// Generally, a fetch is only performed if the submodule commit referenced by the superproject isn't already +/// present in the submodule repository. +/// +/// Note that when unspecified, the `fetch.recurseSubmodules` configuration variable should be used instead. +#[derive(Default, Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum FetchRecurse { + /// Fetch only changed submodules. + #[default] + OnDemand, + /// Fetch all populated submodules, changed or not. + /// + /// This skips the work needed to determine whether a submodule has changed in the first place, but may work + /// more as some fetches might not be necessary. + Always, + /// Submodules are never fetched. + Never, +} + +impl FetchRecurse { + /// Check if `boolean` is set and translate it the respective variant, or check the underlying string + /// value for non-boolean options. + /// On error, it returns the obtained string value which would be the invalid value. + pub fn new(boolean: Result<bool, gix_config::value::Error>) -> Result<Self, BString> { + Ok(match boolean { + Ok(value) => { + if value { + FetchRecurse::Always + } else { + FetchRecurse::Never + } + } + Err(err) => { + if err.input != "on-demand" { + return Err(err.input); + } + FetchRecurse::OnDemand + } + }) + } +} + +/// Describes the branch that should be tracked on the remote. +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum Branch { + /// The name of the remote branch should be the same as the one currently checked out in the superproject. + CurrentInSuperproject, + /// The validated remote-only branch that could be used for fetching. + Name(BString), +} + +impl Default for Branch { + fn default() -> Self { + Branch::Name("HEAD".into()) + } +} + +impl TryFrom<&BStr> for Branch { + type Error = gix_refspec::parse::Error; + + fn try_from(value: &BStr) -> Result<Self, Self::Error> { + if value == "." { + return Ok(Branch::CurrentInSuperproject); + } + + gix_refspec::parse(value, gix_refspec::parse::Operation::Fetch) + .map(|spec| Branch::Name(spec.source().expect("no object").to_owned())) + } +} + +/// Determine how `git submodule update` should deal with this submodule to bring it up-to-date with the +/// super-project's expectations. +#[derive(Default, Debug, Clone, Hash, PartialOrd, PartialEq, Ord, Eq)] +pub enum Update { + /// The commit recorded in the superproject should be checked out on a detached `HEAD`. + #[default] + Checkout, + /// The current branch in the submodule will be rebased onto the commit recorded in the superproject. + Rebase, + /// The commit recorded in the superproject will merged into the current branch of the submodule. + Merge, + /// A custom command to be called like `<command> hash-of-submodule-commit` that is to be executed to + /// perform the submodule update. + /// + /// Note that this variant is only allowed if the value is coming from an override. Thus it's not allowed to distribute + /// arbitrary commands via `.gitmodules` for security reasons. + Command(BString), + /// The submodule update is not performed at all. + None, +} + +impl TryFrom<&BStr> for Update { + type Error = (); + + fn try_from(value: &BStr) -> Result<Self, Self::Error> { + Ok(match value.as_bstr().as_bytes() { + b"checkout" => Update::Checkout, + b"rebase" => Update::Rebase, + b"merge" => Update::Merge, + b"none" => Update::None, + command if command.first() == Some(&b'!') => Update::Command(command[1..].to_owned().into()), + _ => return Err(()), + }) + } +} + +/// The error returned by [File::fetch_recurse()](crate::File::fetch_recurse) and [File::ignore()](crate::File::ignore). +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +#[error("The '{field}' field of submodule '{submodule}' was invalid: '{actual}'")] +pub struct Error { + pub field: &'static str, + pub submodule: BString, + pub actual: BString, +} + +/// +pub mod branch { + use bstr::BString; + + /// The error returned by [File::branch()](crate::File::branch). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + #[error("The value '{actual}' of the 'branch' field of submodule '{submodule}' couldn't be turned into a valid fetch refspec")] + pub struct Error { + pub submodule: BString, + pub actual: BString, + pub source: gix_refspec::parse::Error, + } +} + +/// +pub mod update { + use bstr::BString; + + /// The error returned by [File::update()](crate::File::update). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("The 'update' field of submodule '{submodule}' tried to set command '{actual}' to be shared")] + CommandForbiddenInModulesConfiguration { submodule: BString, actual: BString }, + #[error("The 'update' field of submodule '{submodule}' was invalid: '{actual}'")] + Invalid { submodule: BString, actual: BString }, + } +} + +/// +pub mod url { + use bstr::BString; + + /// The error returned by [File::url()](crate::File::url). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("The url of submodule '{submodule}' could not be parsed")] + Parse { + submodule: BString, + source: gix_url::parse::Error, + }, + #[error("The submodule '{submodule}' was missing its 'url' field or it was empty")] + Missing { submodule: BString }, + } +} + +/// +pub mod path { + use bstr::BString; + + /// The error returned by [File::path()](crate::File::path). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("The path '{actual}' of submodule '{submodule}' needs to be relative")] + Absolute { actual: BString, submodule: BString }, + #[error("The submodule '{submodule}' was missing its 'path' field or it was empty")] + Missing { submodule: BString }, + #[error("The path '{actual}' would lead outside of the repository worktree")] + OutsideOfWorktree { actual: BString, submodule: BString }, + } +} diff --git a/vendor/gix-submodule/src/is_active_platform.rs b/vendor/gix-submodule/src/is_active_platform.rs new file mode 100644 index 000000000..01d72f510 --- /dev/null +++ b/vendor/gix-submodule/src/is_active_platform.rs @@ -0,0 +1,49 @@ +use bstr::BStr; + +use crate::IsActivePlatform; + +/// The error returned by [File::names_and_active_state](crate::File::names_and_active_state()). +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + NormalizePattern(#[from] gix_pathspec::normalize::Error), + #[error(transparent)] + ParsePattern(#[from] gix_pathspec::parse::Error), +} + +impl IsActivePlatform { + /// Returns `true` if the submodule named `name` is active or `false` otherwise. + /// `config` is the configuration that was passed to the originating [modules file](crate::File). + /// `attributes(relative_path, case, is_dir, outcome)` provides a way to resolve the attributes mentioned + /// in `submodule.active` pathspecs that are evaluated in the platforms git configuration. + /// + /// A submodule's active state is determined in the following order + /// + /// * it's `submodule.<name>.active` is set in `config` + /// * it matches a `submodule.active` pathspec either positively or negatively via `:!<spec>` + /// * it's active if it has any `url` set in `config` + pub fn is_active( + &mut self, + config: &gix_config::File<'static>, + name: &BStr, + attributes: &mut dyn FnMut( + &BStr, + gix_pathspec::attributes::glob::pattern::Case, + bool, + &mut gix_pathspec::attributes::search::Outcome, + ) -> bool, + ) -> Result<bool, gix_config::value::Error> { + if let Some(val) = config.boolean("submodule", Some(name), "active").transpose()? { + return Ok(val); + }; + if let Some(val) = self.search.as_mut().map(|search| { + search + .pattern_matching_relative_path(name, Some(true), attributes) + .map_or(false, |m| !m.is_excluded()) + }) { + return Ok(val); + } + Ok(config.string("submodule", Some(name), "url").is_some()) + } +} diff --git a/vendor/gix-submodule/src/lib.rs b/vendor/gix-submodule/src/lib.rs new file mode 100644 index 000000000..639af30fa --- /dev/null +++ b/vendor/gix-submodule/src/lib.rs @@ -0,0 +1,144 @@ +//! Primitives for describing git submodules. +#![deny(rust_2018_idioms, missing_docs)] +#![forbid(unsafe_code)] + +use std::{borrow::Cow, collections::BTreeMap}; + +use bstr::BStr; + +/// All relevant information about a git module, typically from `.gitmodules` files. +/// +/// Note that overrides from other configuration might be relevant, which is why this type +/// can be used to take these into consideration when presented with other configuration +/// from the superproject. +#[derive(Clone)] +pub struct File { + config: gix_config::File<'static>, +} + +mod access; + +/// +pub mod config; + +/// +pub mod is_active_platform; + +/// A platform to keep the state necessary to perform repeated active checks, created by [File::is_active_platform()]. +pub struct IsActivePlatform { + pub(crate) search: Option<gix_pathspec::Search>, +} + +/// Mutation +impl File { + /// This can be used to let `config` override some values we know about submodules, namely… + /// + /// * `url` + /// * `fetchRecurseSubmodules` + /// * `ignore` + /// * `update` + /// * `branch` + /// + /// These values aren't validated yet, which will happen upon query. + pub fn append_submodule_overrides(&mut self, config: &gix_config::File<'_>) -> &mut Self { + let mut values = BTreeMap::<_, Vec<_>>::new(); + for (module_name, section) in config + .sections_by_name("submodule") + .into_iter() + .flatten() + .filter_map(|s| s.header().subsection_name().map(|n| (n, s))) + { + for field in ["url", "fetchRecurseSubmodules", "ignore", "update", "branch"] { + if let Some(value) = section.value(field) { + values.entry((module_name, field)).or_default().push(value); + } + } + } + + let values = { + let mut v: Vec<_> = values.into_iter().collect(); + v.sort_by_key(|a| a.0 .0); + v + }; + + let mut config_to_append = gix_config::File::new(config.meta_owned()); + let mut prev_name = None; + for ((module_name, field), values) in values { + if prev_name.map_or(true, |pn: &BStr| pn != module_name) { + config_to_append + .new_section("submodule", Some(Cow::Owned(module_name.to_owned()))) + .expect("all names come from valid configuration, so remain valid"); + prev_name = Some(module_name); + } + config_to_append + .section_mut("submodule", Some(module_name)) + .expect("always set at this point") + .push( + field.try_into().expect("statically known key"), + Some(values.last().expect("at least one value or we wouldn't be here")), + ); + } + + self.config.append(config_to_append); + self + } +} + +/// +mod init { + use std::path::PathBuf; + + use crate::File; + + impl std::fmt::Debug for File { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("File") + .field("config_path", &self.config_path()) + .field("config", &format_args!("r#\"{}\"#", self.config)) + .finish() + } + } + + /// A marker we use when listing names to not pick them up from overridden sections. + pub(crate) const META_MARKER: gix_config::Source = gix_config::Source::Api; + + /// Lifecycle + impl File { + /// Parse `bytes` as git configuration, typically from `.gitmodules`, without doing any further validation. + /// `path` can be provided to keep track of where the file was read from in the underlying [`config`](Self::config()) + /// instance. + /// `config` is used to [apply value overrides](File::append_submodule_overrides), which can be empty if overrides + /// should be applied at a later time. + /// + /// Future access to the module information is lazy and configuration errors are exposed there on a per-value basis. + /// + /// ### Security Considerations + /// + /// The information itself should be used with care as it can direct the caller to fetch from remotes. It is, however, + /// on the caller to assure the input data can be trusted. + pub fn from_bytes( + bytes: &[u8], + path: impl Into<Option<PathBuf>>, + config: &gix_config::File<'_>, + ) -> Result<Self, gix_config::parse::Error> { + let metadata = { + let mut meta = gix_config::file::Metadata::from(META_MARKER); + meta.path = path.into(); + meta + }; + let modules = gix_config::File::from_parse_events_no_includes( + gix_config::parse::Events::from_bytes_owned(bytes, None)?, + metadata, + ); + + let mut res = Self { config: modules }; + res.append_submodule_overrides(config); + Ok(res) + } + + /// Turn ourselves into the underlying parsed configuration file. + pub fn into_config(self) -> gix_config::File<'static> { + self.config + } + } +} |