gix_submodule/
lib.rs

1//! Primitives for describing git submodules.
2#![deny(rust_2018_idioms, missing_docs)]
3#![forbid(unsafe_code)]
4
5use std::{borrow::Cow, collections::BTreeMap};
6
7/// All relevant information about a git module, typically from `.gitmodules` files.
8///
9/// Note that overrides from other configuration might be relevant, which is why this type
10/// can be used to take these into consideration when presented with other configuration
11/// from the superproject.
12#[derive(Clone)]
13pub struct File {
14    config: gix_config::File<'static>,
15}
16
17mod access;
18
19///
20pub mod config;
21
22///
23pub mod is_active_platform;
24
25/// A platform to keep the state necessary to perform repeated active checks, created by [File::is_active_platform()].
26pub struct IsActivePlatform {
27    pub(crate) search: Option<gix_pathspec::Search>,
28}
29
30/// Mutation
31impl File {
32    /// This can be used to let `config` override some values we know about submodules, namely…
33    ///
34    /// * `url`
35    /// * `fetchRecurseSubmodules`
36    /// * `ignore`
37    /// * `update`
38    /// * `branch`
39    ///
40    /// These values aren't validated yet, which will happen upon query.
41    pub fn append_submodule_overrides(&mut self, config: &gix_config::File<'_>) -> &mut Self {
42        let mut values = BTreeMap::<_, Vec<_>>::new();
43        for (module_name, section) in config
44            .sections_by_name("submodule")
45            .into_iter()
46            .flatten()
47            .filter_map(|s| s.header().subsection_name().map(|n| (n, s)))
48        {
49            for field in ["url", "fetchRecurseSubmodules", "ignore", "update", "branch"] {
50                if let Some(value) = section.value(field) {
51                    values.entry((module_name, field)).or_default().push(value);
52                }
53            }
54        }
55
56        let values = {
57            let mut v: Vec<_> = values.into_iter().collect();
58            v.sort_by_key(|a| a.0 .0);
59            v
60        };
61
62        let mut config_to_append = gix_config::File::new(config.meta_owned());
63        let mut prev_name = None;
64        for ((module_name, field), values) in values {
65            if prev_name != Some(module_name) {
66                config_to_append
67                    .new_section("submodule", Some(Cow::Owned(module_name.to_owned())))
68                    .expect("all names come from valid configuration, so remain valid");
69                prev_name = Some(module_name);
70            }
71            config_to_append
72                .section_mut("submodule", Some(module_name))
73                .expect("always set at this point")
74                .push(
75                    field.try_into().expect("statically known key"),
76                    Some(values.last().expect("at least one value or we wouldn't be here")),
77                );
78        }
79
80        self.config.append(config_to_append);
81        self
82    }
83}
84
85///
86mod init {
87    use std::path::PathBuf;
88
89    use crate::File;
90
91    impl std::fmt::Debug for File {
92        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93            f.debug_struct("File")
94                .field("config_path", &self.config_path())
95                .field("config", &format_args!("r#\"{}\"#", self.config))
96                .finish()
97        }
98    }
99
100    /// A marker we use when listing names to not pick them up from overridden sections.
101    pub(crate) const META_MARKER: gix_config::Source = gix_config::Source::Api;
102
103    /// Lifecycle
104    impl File {
105        /// Parse `bytes` as git configuration, typically from `.gitmodules`, without doing any further validation.
106        /// `path` can be provided to keep track of where the file was read from in the underlying [`config`](Self::config())
107        /// instance.
108        /// `config` is used to [apply value overrides](File::append_submodule_overrides), which can be empty if overrides
109        /// should be applied at a later time.
110        ///
111        /// Future access to the module information is lazy and configuration errors are exposed there on a per-value basis.
112        ///
113        /// ### Security Considerations
114        ///
115        /// The information itself should be used with care as it can direct the caller to fetch from remotes. It is, however,
116        /// on the caller to assure the input data can be trusted.
117        pub fn from_bytes(
118            bytes: &[u8],
119            path: impl Into<Option<PathBuf>>,
120            config: &gix_config::File<'_>,
121        ) -> Result<Self, gix_config::parse::Error> {
122            let metadata = {
123                let mut meta = gix_config::file::Metadata::from(META_MARKER);
124                meta.path = path.into();
125                meta
126            };
127            let modules = gix_config::File::from_parse_events_no_includes(
128                gix_config::parse::Events::from_bytes_owned(bytes, None)?,
129                metadata,
130            );
131
132            let mut res = Self { config: modules };
133            res.append_submodule_overrides(config);
134            Ok(res)
135        }
136
137        /// Turn ourselves into the underlying parsed configuration file.
138        pub fn into_config(self) -> gix_config::File<'static> {
139            self.config
140        }
141    }
142}