Skip to content

Commit c5955fc

Browse files
authored
Merge pull request #1656 from GitoxideLabs/hasconfig
hasconfig:remote.*.url
2 parents 1411289 + d51aec9 commit c5955fc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+421
-130
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crate-status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ See its [README.md](https://github.com/GitoxideLabs/gitoxide/blob/main/gix-lock/
747747
* all config values as per the `gix-config-value` crate
748748
* **includeIf**
749749
* [x] `gitdir`, `gitdir/i`, and `onbranch`
750-
* [ ] `hasconfig`
750+
* [x] `hasconfig:remote.*.url`
751751
* [x] access values and sections by name and sub-section
752752
* [x] edit configuration in memory, non-destructively
753753
* cross-platform newline handling

gix-config/src/file/includes/mod.rs

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,16 @@ impl File<'static> {
2323
/// times. It's recommended use is as part of a multi-step bootstrapping which needs fine-grained control,
2424
/// and unless that's given one should prefer one of the other ways of initialization that resolve includes
2525
/// at the right time.
26+
///
27+
/// # Deviation
28+
///
2629
/// - included values are added after the _section_ that included them, not directly after the value. This is
2730
/// a deviation from how git does it, as it technically adds new value right after the include path itself,
2831
/// technically 'splitting' the section. This can only make a difference if the `include` section also has values
2932
/// which later overwrite portions of the included file, which seems unusual as these would be related to `includes`.
3033
/// We can fix this by 'splitting' the include section if needed so the included sections are put into the right place.
34+
/// - `hasconfig:remote.*.url` will not prevent itself to include files with `[remote "name"]\nurl = x` values, but it also
35+
/// won't match them, i.e. one cannot include something that will cause the condition to match or to always be true.
3136
pub fn resolve_includes(&mut self, options: init::Options<'_>) -> Result<(), Error> {
3237
if options.includes.max_depth == 0 {
3338
return Ok(());
@@ -38,10 +43,11 @@ impl File<'static> {
3843
}
3944

4045
pub(crate) fn resolve(config: &mut File<'static>, buf: &mut Vec<u8>, options: init::Options<'_>) -> Result<(), Error> {
41-
resolve_includes_recursive(config, 0, buf, options)
46+
resolve_includes_recursive(None, config, 0, buf, options)
4247
}
4348

4449
fn resolve_includes_recursive(
50+
search_config: Option<&File<'static>>,
4551
target_config: &mut File<'static>,
4652
depth: u8,
4753
buf: &mut Vec<u8>,
@@ -57,30 +63,34 @@ fn resolve_includes_recursive(
5763
};
5864
}
5965

60-
let mut section_ids_and_include_paths = Vec::new();
61-
for (id, section) in target_config
62-
.section_order
63-
.iter()
64-
.map(|id| (*id, &target_config.sections[id]))
65-
{
66+
for id in target_config.section_order.clone().into_iter() {
67+
let section = &target_config.sections[&id];
6668
let header = &section.header;
6769
let header_name = header.name.as_ref();
70+
let mut paths = None;
6871
if header_name == "include" && header.subsection_name.is_none() {
69-
detach_include_paths(&mut section_ids_and_include_paths, section, id);
72+
paths = Some(gather_paths(section, id));
7073
} else if header_name == "includeIf" {
7174
if let Some(condition) = &header.subsection_name {
7275
let target_config_path = section.meta.path.as_deref();
73-
if include_condition_match(condition.as_ref(), target_config_path, options.includes)? {
74-
detach_include_paths(&mut section_ids_and_include_paths, section, id);
76+
if include_condition_match(
77+
condition.as_ref(),
78+
target_config_path,
79+
search_config.unwrap_or(target_config),
80+
options.includes,
81+
)? {
82+
paths = Some(gather_paths(section, id));
7583
}
7684
}
7785
}
86+
if let Some(paths) = paths {
87+
insert_includes_recursively(paths, target_config, depth, options, buf)?;
88+
}
7889
}
79-
80-
append_followed_includes_recursively(section_ids_and_include_paths, target_config, depth, options, buf)
90+
Ok(())
8191
}
8292

83-
fn append_followed_includes_recursively(
93+
fn insert_includes_recursively(
8494
section_ids_and_include_paths: Vec<(SectionId, crate::Path<'_>)>,
8595
target_config: &mut File<'static>,
8696
depth: u8,
@@ -124,30 +134,26 @@ fn append_followed_includes_recursively(
124134
init::Error::Interpolate(err) => Error::Interpolate(err),
125135
init::Error::Includes(_) => unreachable!("BUG: {:?} not possible due to no-follow options", err),
126136
})?;
127-
resolve_includes_recursive(&mut include_config, depth + 1, buf, options)?;
137+
resolve_includes_recursive(Some(target_config), &mut include_config, depth + 1, buf, options)?;
128138

129139
target_config.append_or_insert(include_config, Some(section_id));
130140
}
131141
Ok(())
132142
}
133143

134-
fn detach_include_paths(
135-
include_paths: &mut Vec<(SectionId, crate::Path<'static>)>,
136-
section: &file::Section<'_>,
137-
id: SectionId,
138-
) {
139-
include_paths.extend(
140-
section
141-
.body
142-
.values("path")
143-
.into_iter()
144-
.map(|path| (id, crate::Path::from(Cow::Owned(path.into_owned())))),
145-
);
144+
fn gather_paths(section: &file::Section<'_>, id: SectionId) -> Vec<(SectionId, crate::Path<'static>)> {
145+
section
146+
.body
147+
.values("path")
148+
.into_iter()
149+
.map(|path| (id, crate::Path::from(Cow::Owned(path.into_owned()))))
150+
.collect()
146151
}
147152

148153
fn include_condition_match(
149154
condition: &BStr,
150155
target_config_path: Option<&Path>,
156+
search_config: &File<'static>,
151157
options: Options<'_>,
152158
) -> Result<bool, Error> {
153159
let mut tokens = condition.splitn(2, |b| *b == b':');
@@ -170,6 +176,32 @@ fn include_condition_match(
170176
gix_glob::wildmatch::Mode::IGNORE_CASE,
171177
),
172178
b"onbranch" => Ok(onbranch_matches(condition, options.conditional).is_some()),
179+
b"hasconfig" => {
180+
let mut tokens = condition.splitn(2, |b| *b == b':');
181+
let (key_glob, value_glob) = match (tokens.next(), tokens.next()) {
182+
(Some(a), Some(b)) => (a, b),
183+
_ => return Ok(false),
184+
};
185+
if key_glob.as_bstr() != "remote.*.url" {
186+
return Ok(false);
187+
}
188+
let Some(sections) = search_config.sections_by_name("remote") else {
189+
return Ok(false);
190+
};
191+
for remote in sections {
192+
for url in remote.values("url") {
193+
let glob_matches = gix_glob::wildmatch(
194+
value_glob.as_bstr(),
195+
url.as_ref(),
196+
gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL,
197+
);
198+
if glob_matches {
199+
return Ok(true);
200+
}
201+
}
202+
}
203+
Ok(false)
204+
}
173205
_ => Ok(false),
174206
}
175207
}

gix-config/tests/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ publish = false
1414

1515
[[test]]
1616
name = "config"
17-
path = "config.rs"
17+
path = "config/mod.rs"
1818

1919
[[test]]
2020
name = "mem"
@@ -23,7 +23,6 @@ path = "mem.rs"
2323
[dev-dependencies]
2424
gix-config = { path = ".." }
2525
gix-testtools = { path = "../../tests/tools" }
26-
gix = { path = "../../gix", default-features = false }
2726
gix-ref = { path = "../../gix-ref" }
2827
gix-path = { path = "../../gix-path" }
2928
gix-sec = { path = "../../gix-sec" }

0 commit comments

Comments
 (0)