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