Skip to main content

gix_submodule/
lib.rs

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