1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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
        }
    }
}