1use std::{borrow::Cow, collections::HashSet, path::Path};
2
3use bstr::BStr;
4
5use crate::{
6 File, IsActivePlatform, config,
7 config::{Branch, FetchRecurse, Ignore, Update},
8};
9
10impl File {
15 pub fn config(&self) -> &gix_config::File<'static> {
20 &self.config
21 }
22
23 pub fn config_path(&self) -> Option<&Path> {
25 self.config.sections().filter_map(|s| s.meta().path.as_deref()).next()
26 }
27
28 pub fn names(&self) -> impl Iterator<Item = &BStr> {
32 let mut seen = HashSet::<&BStr>::default();
33 self.config
34 .sections_by_name("submodule")
35 .into_iter()
36 .flatten()
37 .filter_map(move |s| {
38 s.header()
39 .subsection_name()
40 .filter(|_| s.meta().source == crate::init::META_MARKER)
41 .filter(|name| seen.insert(*name))
42 })
43 }
44
45 pub fn names_and_active_state<'a>(
47 &'a self,
48 config: &'a gix_config::File<'static>,
49 defaults: gix_pathspec::Defaults,
50 attributes: &'a mut (
51 dyn FnMut(
52 &BStr,
53 gix_pathspec::attributes::glob::pattern::Case,
54 bool,
55 &mut gix_pathspec::attributes::search::Outcome,
56 ) -> bool
57 + 'a
58 ),
59 ) -> Result<
60 impl Iterator<Item = (&'a BStr, Result<bool, gix_config::value::Error>)> + 'a,
61 crate::is_active_platform::Error,
62 > {
63 let mut platform = self.is_active_platform(config, defaults)?;
64 let iter = self
65 .names()
66 .map(move |name| (name, platform.is_active(config, name, attributes)));
67 Ok(iter)
68 }
69
70 pub fn is_active_platform(
77 &self,
78 config: &gix_config::File<'_>,
79 defaults: gix_pathspec::Defaults,
80 ) -> Result<IsActivePlatform, crate::is_active_platform::Error> {
81 let search = config
82 .strings("submodule.active")
83 .map(|patterns| -> Result<_, crate::is_active_platform::Error> {
84 let patterns = patterns
85 .into_iter()
86 .map(|pattern| gix_pathspec::parse(&pattern, defaults))
87 .collect::<Result<Vec<_>, _>>()?;
88 Ok(gix_pathspec::Search::from_specs(
89 patterns,
90 None,
91 std::path::Path::new(""),
92 )?)
93 })
94 .transpose()?;
95 Ok(IsActivePlatform { search })
96 }
97
98 pub fn name_by_path(&self, relative_path: &BStr) -> Option<&BStr> {
104 self.names()
105 .filter_map(|n| self.path(n).ok().map(|p| (n, p)))
106 .find_map(|(n, p)| (p == relative_path).then_some(n))
107 }
108}
109
110impl File {
112 pub fn path(&self, name: &BStr) -> Result<Cow<'_, BStr>, config::path::Error> {
121 let path_bstr =
122 self.config
123 .string(&format!("submodule.{name}.path"))
124 .ok_or_else(|| config::path::Error::Missing {
125 submodule: name.to_owned(),
126 })?;
127 if path_bstr.is_empty() {
128 return Err(config::path::Error::Missing {
129 submodule: name.to_owned(),
130 });
131 }
132 let path = gix_path::from_bstr(path_bstr.as_ref());
133 if path.is_absolute() {
134 return Err(config::path::Error::Absolute {
135 submodule: name.to_owned(),
136 actual: path_bstr.into_owned(),
137 });
138 }
139 if gix_path::normalize(path, "".as_ref()).is_none() {
140 return Err(config::path::Error::OutsideOfWorktree {
141 submodule: name.to_owned(),
142 actual: path_bstr.into_owned(),
143 });
144 }
145 Ok(path_bstr)
146 }
147
148 pub fn url(&self, name: &BStr) -> Result<gix_url::Url, config::url::Error> {
150 let url = self
151 .config
152 .string(&format!("submodule.{name}.url"))
153 .ok_or_else(|| config::url::Error::Missing {
154 submodule: name.to_owned(),
155 })?;
156
157 if url.is_empty() {
158 return Err(config::url::Error::Missing {
159 submodule: name.to_owned(),
160 });
161 }
162 gix_url::Url::from_bytes(url.as_ref()).map_err(|err| config::url::Error::Parse {
163 submodule: name.to_owned(),
164 source: err,
165 })
166 }
167
168 pub fn update(&self, name: &BStr) -> Result<Option<Update>, config::update::Error> {
170 let mut value_is_from_modules_file = None;
171 let our_meta = self.config.meta();
172 let value: Update = match self.config.string_filter(&format!("submodule.{name}.update"), |meta| {
173 value_is_from_modules_file = Some(std::ptr::eq(meta, our_meta));
174 true
175 }) {
176 Some(v) => v.as_ref().try_into().map_err(|()| config::update::Error::Invalid {
177 submodule: name.to_owned(),
178 actual: v.into_owned(),
179 })?,
180 None => return Ok(None),
181 };
182
183 if let Update::Command(cmd) = &value {
184 if value_is_from_modules_file.unwrap_or_default() {
185 return Err(config::update::Error::CommandForbiddenInModulesConfiguration {
186 submodule: name.to_owned(),
187 actual: cmd.to_owned(),
188 });
189 }
190 }
191 Ok(Some(value))
192 }
193
194 pub fn branch(&self, name: &BStr) -> Result<Option<Branch>, config::branch::Error> {
198 let branch = match self.config.string(&format!("submodule.{name}.branch")) {
199 Some(v) => v,
200 None => return Ok(None),
201 };
202
203 Branch::try_from(branch.as_ref())
204 .map(Some)
205 .map_err(|err| config::branch::Error {
206 submodule: name.to_owned(),
207 actual: branch.into_owned(),
208 source: err,
209 })
210 }
211
212 pub fn fetch_recurse(&self, name: &BStr) -> Result<Option<FetchRecurse>, config::Error> {
216 self.config
217 .boolean(&format!("submodule.{name}.fetchRecurseSubmodules"))
218 .map(FetchRecurse::new)
219 .transpose()
220 .map_err(|value| config::Error {
221 field: "fetchRecurseSubmodules",
222 submodule: name.to_owned(),
223 actual: value,
224 })
225 }
226
227 pub fn ignore(&self, name: &BStr) -> Result<Option<Ignore>, config::Error> {
229 self.config
230 .string(&format!("submodule.{name}.ignore"))
231 .map(|value| {
232 Ignore::try_from(value.as_ref()).map_err(|()| config::Error {
233 field: "ignore",
234 submodule: name.to_owned(),
235 actual: value.into_owned(),
236 })
237 })
238 .transpose()
239 }
240
241 pub fn shallow(&self, name: &BStr) -> Result<Option<bool>, gix_config::value::Error> {
245 self.config.boolean(&format!("submodule.{name}.shallow")).transpose()
246 }
247}