| //! 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; |
| |
| /// |
| #[allow(clippy::empty_docs)] |
| pub mod config; |
| |
| /// |
| #[allow(clippy::empty_docs)] |
| 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 |
| } |
| } |
| |
| /// |
| #[allow(clippy::empty_docs)] |
| 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 |
| } |
| } |
| } |