fn submodule(bytes: &str) -> gix_submodule::File { gix_submodule::File::from_bytes(bytes.as_bytes(), None, &Default::default()).expect("valid module")} mod is_active_platform { use std::str::FromStr; fn module_file(name: &str) -> crate::Result<gix_submodule::File> { let modules = gix_testtools::scripted_fixture_read_only("basic.sh")? .join(name) .join(".gitmodules"); Ok(gix_submodule::File::from_bytes( std::fs::read(&modules)?.as_slice(), modules, &Default::default(), )?) } use bstr::{BStr, ByteSlice}; fn multi_modules() -> crate::Result<gix_submodule::File> { module_file("multiple") } fn assume_valid_active_state<'a>( module: &'a gix_submodule::File, config: &'a gix_config::File, defaults: gix_pathspec::Defaults, ) -> crate::Result<Vec<(&'a str, bool)>> { assume_valid_active_state_with_attrs(module, config, defaults, |_, _, _, _| { unreachable!("shouldn't be called") }) } fn assume_valid_active_state_with_attrs<'a>( module: &'a gix_submodule::File, config: &'a gix_config::File, defaults: gix_pathspec::Defaults, mut attributes: impl FnMut( &BStr, gix_pathspec::attributes::glob::pattern::Case, bool, &mut gix_pathspec::attributes::search::Outcome, ) -> bool + 'a, ) -> crate::Result<Vec<(&'a str, bool)>> { let mut platform = module.is_active_platform(config, defaults)?; Ok(module .names() .map(|name| { ( name.to_str().expect("valid"), platform.is_active(config, name, &mut attributes).expect("valid"), ) }) .collect()) } #[test] fn without_submodule_in_index() -> crate::Result { let module = module_file("not-a-submodule")?; assert_eq!( module.names().map(ToOwned::to_owned).collect::<Vec<_>>(), ["submodule"], "entries can be read" ); Ok(()) } #[test] fn without_any_additional_settings_all_are_inactive_if_they_have_a_url() -> crate::Result { let module = multi_modules()?; assert_eq!( assume_valid_active_state(&module, &Default::default(), Default::default())?, &[ ("submodule", false), ("a/b", false), (".a/..c", false), (r"a/d\", false), (r"a\e", false) ] ); Ok(()) } #[test] fn submodules_with_active_config_are_considered_active_or_inactive() -> crate::Result { let module = multi_modules()?; assert_eq!( assume_valid_active_state( &module, &gix_config::File::from_str( "[submodule.submodule]\n active = 0\n url = set \n[submodule \"a/b\"]\n active = false \n url = set \n[submodule \".a/..c\"] active = 1" )?, Default::default() )?, &[ ("submodule", false), ("a/b", false), (".a/..c", true), (r"a/d\", false), (r"a\e", false) ] ); Ok(()) } #[test] fn submodules_with_active_config_override_pathspecs() -> crate::Result { let module = multi_modules()?; assert_eq!( assume_valid_active_state( &module, &gix_config::File::from_str( "[submodule.submodule]\n active = 0\n[submodule]\n active = *\n[submodule]\n active = :!a*" )?, Default::default() )?, &[ ("submodule", false), ("a/b", false), (".a/..c", true), (r"a/d\", false), (r"a\e", false) ] ); Ok(()) } #[test] fn pathspecs_matter_even_if_they_do_not_match() -> crate::Result { let module = multi_modules()?; assert_eq!( assume_valid_active_state( &module, &gix_config::File::from_str("[submodule]\n active = submodule ")?, Default::default() )?, &[ ("submodule", true), ("a/b", false), (".a/..c", false), (r"a/d\", false), (r"a\e", false) ] ); assert_eq!( assume_valid_active_state( &module, &gix_config::File::from_str("[submodule]\n active = :!submodule ")?, Default::default() )?, &[ ("submodule", false), ("a/b", true), (".a/..c", true), (r"a/d\", true), (r"a\e", true) ] ); Ok(()) }} mod path { use gix_submodule::config::path::Error; use crate::file::submodule; fn submodule_path(value: &str) -> Error { let module = submodule(&format!("[submodule.a]\npath = {value}")); module.path("a".into()).unwrap_err() } #[test] fn valid() -> crate::Result { let module = submodule("[submodule.a]\n path = relative/path/submodule"); assert_eq!(module.path("a".into())?, "relative/path/submodule"); Ok(()) } #[test] fn validate_upon_retrieval() { assert!(matches!( submodule_path(if cfg!(windows) { r"c:\\hello" } else { r"/definitely/absolute\\" }), Error::Absolute { .. } )); assert!(matches!(submodule_path(""), Error::Missing { .. })); assert!(matches!(submodule_path("../attack"), Error::OutsideOfWorktree { .. })); { let module = submodule("[submodule.a]\n path"); assert!(matches!(module.path("a".into()).unwrap_err(), Error::Missing { .. })); } { let module = submodule("[submodule.a]\n"); assert!(matches!(module.path("a".into()).unwrap_err(), Error::Missing { .. })); } }} mod url { use gix_submodule::config::url::Error; use crate::file::submodule; fn submodule_url(value: &str) -> Error { let module = submodule(&format!("[submodule.a]\nurl = {value}")); module.url("a".into()).unwrap_err() } #[test] fn valid() -> crate::Result { let module = submodule("[submodule.a]\n url = path-to-repo"); assert_eq!(module.url("a".into())?.to_bstring(), "path-to-repo"); Ok(()) } #[test] fn validate_upon_retrieval() { assert!(matches!(submodule_url(""), Error::Missing { .. })); { let module = submodule("[submodule.a]\n url"); assert!(matches!(module.url("a".into()).unwrap_err(), Error::Missing { .. })); } { let module = submodule("[submodule.a]\n"); assert!(matches!(module.url("a".into()).unwrap_err(), Error::Missing { .. })); } assert!(matches!(submodule_url("file://"), Error::Parse { .. })); }} mod update { use std::str::FromStr; use gix_submodule::config::{Update, update::Error}; use crate::file::submodule; fn submodule_update(value: &str) -> Error { let module = submodule(&format!("[submodule.a]\nupdate = {value}")); module.update("a".into()).unwrap_err() } #[test] fn default() { assert_eq!(Update::default(), Update::Checkout, "as defined in the docs"); } #[test] fn valid() -> crate::Result { for (valid, expected) in [ ("checkout", Update::Checkout), ("rebase", Update::Rebase), ("merge", Update::Merge), ("none", Update::None), ] { let module = submodule(&format!("[submodule.a]\n update = {valid}")); assert_eq!(module.update("a".into())?.expect("present"), expected); } Ok(()) } #[test] fn valid_in_overrides() -> crate::Result { let mut module = submodule("[submodule.a]\n update = merge"); let repo_config = gix_config::File::from_str("[submodule.a]\n update = !dangerous")?; let prev_names = module.names().map(ToOwned::to_owned).collect::<Vec<_>>(); module .append_submodule_overrides(&repo_config) .expect("the fixture fits into the backing buffer"); assert_eq!( module.update("a".into())?.expect("present"), Update::Command("dangerous".into()), "overridden values are picked up and make commands possible - these are local" ); assert_eq!( module.names().map(ToOwned::to_owned).collect::<Vec<_>>(), prev_names, "Appending more configuration sections doesn't affect name listing" ); Ok(()) } #[test] fn validate_upon_retrieval() { assert!(matches!(submodule_update(""), Error::Invalid { .. })); assert!(matches!(submodule_update("bogus"), Error::Invalid { .. })); assert!( matches!( submodule_update("!dangerous"), Error::CommandForbiddenInModulesConfiguration { .. } ), "forbidden unless it's an override" ); } /// Reproducer for GHSA-f26g-jm89-4g65 and GHSA-97pq-9mjg-9fcj: `.gitmodules` may carry /// `submodule.<name>.update = !command`, while a same-named local section without the winning /// `update` value makes `File::update()` treat the command as trusted and expose it as /// `Update::Command`. #[test] fn modules_command_is_authorized_by_unrelated_same_named_override() -> crate::Result { let mut module = submodule("[submodule.a]\n update = !dangerous"); let repo_config = gix_config::File::from_str("[submodule.a]\n url = trusted-local-override")?; module .append_submodule_overrides(&repo_config) .expect("the fixture fits into the backing buffer"); assert!( matches!( module.update("a".into()), Err(Error::CommandForbiddenInModulesConfiguration { actual, .. }) if actual == "dangerous" ), "a same-named local section must not authorize a command that still originates from .gitmodules" ); Ok(()) }} mod fetch_recurse { use gix_submodule::config::FetchRecurse; use crate::file::submodule; #[test] fn default() { assert_eq!( FetchRecurse::default(), FetchRecurse::OnDemand, "as defined in git codebase actually" ); } #[test] fn valid() -> crate::Result { for (valid, expected) in [ ("yes", FetchRecurse::Always), ("true", FetchRecurse::Always), ("", FetchRecurse::Never), ("no", FetchRecurse::Never), ("false", FetchRecurse::Never), ("on-demand", FetchRecurse::OnDemand), ] { let module = submodule(&format!("[submodule.a]\n fetchRecurseSubmodules = {valid}")); assert_eq!(module.fetch_recurse("a".into())?.expect("present"), expected); } let module = submodule("[submodule.a]\n fetchRecurseSubmodules"); assert_eq!( module.fetch_recurse("a".into())?.expect("present"), FetchRecurse::Always, "no value means true, which means to always recurse" ); Ok(()) } #[test] fn validate_upon_retrieval() -> crate::Result { for invalid in ["foo", "ney", "On-demand"] { let module = submodule(&format!("[submodule.a]\n fetchRecurseSubmodules = \"{invalid}\"")); assert!(module.fetch_recurse("a".into()).is_err()); } Ok(()) }} mod ignore { use gix_submodule::config::Ignore; use crate::file::submodule; #[test] fn default() { assert_eq!(Ignore::default(), Ignore::None, "as defined in the docs"); } #[test] fn valid() -> crate::Result { for (valid, expected) in [ ("all", Ignore::All), ("dirty", Ignore::Dirty), ("untracked", Ignore::Untracked), ("none", Ignore::None), ] { let module = submodule(&format!("[submodule.a]\n ignore = {valid}")); assert_eq!(module.ignore("a".into())?.expect("present"), expected); } let module = submodule("[submodule.a]\n ignore"); assert!( module.ignore("a".into())?.is_none(), "no value is interpreted as non-existing string, hence the caller will see None" ); Ok(()) } #[test] fn validate_upon_retrieval() -> crate::Result { for invalid in ["All", ""] { let module = submodule(&format!("[submodule.a]\n ignore = \"{invalid}\"")); assert!(module.ignore("a".into()).is_err()); } Ok(()) }} mod branch { use gix_submodule::config::Branch; use crate::file::submodule; #[test] fn valid() -> crate::Result { for (valid, expected) in [ (".", Branch::CurrentInSuperproject), ("", Branch::Name("HEAD".into())), ("master", Branch::Name("master".into())), ("feature/a", Branch::Name("feature/a".into())), ( "abcde12345abcde12345abcde12345abcde12345", Branch::Name("abcde12345abcde12345abcde12345abcde12345".into()), ), ] { let module = submodule(&format!("[submodule.a]\n branch = {valid}")); assert_eq!(module.branch("a".into())?.expect("present"), expected); } let module = submodule("[submodule.a]\n branch"); assert!( module.branch("a".into())?.is_none(), "no value implies it's not set, but the caller will then default" ); Ok(()) } #[test] fn validate_upon_retrieval() -> crate::Result { let module = submodule("[submodule.a]\n branch = /invalid"); assert!(module.branch("a".into()).is_err()); Ok(()) }} #[test]fn shallow() -> crate::Result { let module = submodule("[submodule.a]\n shallow"); assert_eq!( module.shallow("a".into())?, Some(true), "shallow is a simple boolean without anything special (yet)" ); Ok(())} mod append_submodule_overrides { use std::str::FromStr; use crate::file::submodule; #[test] fn last_of_multiple_values_wins() -> crate::Result { let mut module = submodule("[submodule.a] url = from-module"); let repo_config = gix_config::File::from_str( "[submodule.a]\n url = a\n url = b\n ignore = x\n [submodule.a]\n url = c\n[submodule.b] url = not-relevant", )?; module .append_submodule_overrides(&repo_config) .expect("the fixture fits into the backing buffer"); Ok(()) }} mod baseline;
fn submodule(bytes: &str) -> gix_submodule::File {
gix_submodule::File::from_bytes(bytes.as_bytes(), None, &Default::default()).expect("valid module")
}
mod is_active_platform {
use std::str::FromStr;
fn module_file(name: &str) -> crate::Result<gix_submodule::File> {
let modules = gix_testtools::scripted_fixture_read_only("basic.sh")?
.join(name)
.join(".gitmodules");
Ok(gix_submodule::File::from_bytes(
std::fs::read(&modules)?.as_slice(),
modules,
&Default::default(),
)?)
}
use bstr::{BStr, ByteSlice};
fn multi_modules() -> crate::Result<gix_submodule::File> {
module_file("multiple")
}
fn assume_valid_active_state<'a>(
module: &'a gix_submodule::File,
config: &'a gix_config::File,
defaults: gix_pathspec::Defaults,
) -> crate::Result<Vec<(&'a str, bool)>> {
assume_valid_active_state_with_attrs(module, config, defaults, |_, _, _, _| {
unreachable!("shouldn't be called")
})
}
fn assume_valid_active_state_with_attrs<'a>(
module: &'a gix_submodule::File,
config: &'a gix_config::File,
defaults: gix_pathspec::Defaults,
mut attributes: impl FnMut(
&BStr,
gix_pathspec::attributes::glob::pattern::Case,
bool,
&mut gix_pathspec::attributes::search::Outcome,
) -> bool
+ 'a,
) -> crate::Result<Vec<(&'a str, bool)>> {
let mut platform = module.is_active_platform(config, defaults)?;
Ok(module
.names()
.map(|name| {
(
name.to_str().expect("valid"),
platform.is_active(config, name, &mut attributes).expect("valid"),
)
})
.collect())
}
#[test]
fn without_submodule_in_index() -> crate::Result {
let module = module_file("not-a-submodule")?;
assert_eq!(
module.names().map(ToOwned::to_owned).collect::<Vec<_>>(),
["submodule"],
"entries can be read"
);
Ok(())
}
#[test]
fn without_any_additional_settings_all_are_inactive_if_they_have_a_url() -> crate::Result {
let module = multi_modules()?;
assert_eq!(
assume_valid_active_state(&module, &Default::default(), Default::default())?,
&[
("submodule", false),
("a/b", false),
(".a/..c", false),
(r"a/d\", false),
(r"a\e", false)
]
);
Ok(())
}
#[test]
fn submodules_with_active_config_are_considered_active_or_inactive() -> crate::Result {
let module = multi_modules()?;
assert_eq!(
assume_valid_active_state(
&module,
&gix_config::File::from_str(
"[submodule.submodule]\n active = 0\n url = set \n[submodule \"a/b\"]\n active = false \n url = set \n[submodule \".a/..c\"] active = 1"
)?,
Default::default()
)?,
&[
("submodule", false),
("a/b", false),
(".a/..c", true),
(r"a/d\", false),
(r"a\e", false)
]
);
Ok(())
}
#[test]
fn submodules_with_active_config_override_pathspecs() -> crate::Result {
let module = multi_modules()?;
assert_eq!(
assume_valid_active_state(
&module,
&gix_config::File::from_str(
"[submodule.submodule]\n active = 0\n[submodule]\n active = *\n[submodule]\n active = :!a*"
)?,
Default::default()
)?,
&[
("submodule", false),
("a/b", false),
(".a/..c", true),
(r"a/d\", false),
(r"a\e", false)
]
);
Ok(())
}
#[test]
fn pathspecs_matter_even_if_they_do_not_match() -> crate::Result {
let module = multi_modules()?;
assert_eq!(
assume_valid_active_state(
&module,
&gix_config::File::from_str("[submodule]\n active = submodule ")?,
Default::default()
)?,
&[
("submodule", true),
("a/b", false),
(".a/..c", false),
(r"a/d\", false),
(r"a\e", false)
]
);
assert_eq!(
assume_valid_active_state(
&module,
&gix_config::File::from_str("[submodule]\n active = :!submodule ")?,
Default::default()
)?,
&[
("submodule", false),
("a/b", true),
(".a/..c", true),
(r"a/d\", true),
(r"a\e", true)
]
);
Ok(())
}
}
mod path {
use gix_submodule::config::path::Error;
use crate::file::submodule;
fn submodule_path(value: &str) -> Error {
let module = submodule(&format!("[submodule.a]\npath = {value}"));
module.path("a".into()).unwrap_err()
}
#[test]
fn valid() -> crate::Result {
let module = submodule("[submodule.a]\n path = relative/path/submodule");
assert_eq!(module.path("a".into())?, "relative/path/submodule");
Ok(())
}
#[test]
fn validate_upon_retrieval() {
assert!(matches!(
submodule_path(if cfg!(windows) {
r"c:\\hello"
} else {
r"/definitely/absolute\\"
}),
Error::Absolute { .. }
));
assert!(matches!(submodule_path(""), Error::Missing { .. }));
assert!(matches!(submodule_path("../attack"), Error::OutsideOfWorktree { .. }));
{
let module = submodule("[submodule.a]\n path");
assert!(matches!(module.path("a".into()).unwrap_err(), Error::Missing { .. }));
}
{
let module = submodule("[submodule.a]\n");
assert!(matches!(module.path("a".into()).unwrap_err(), Error::Missing { .. }));
}
}
}
mod url {
use gix_submodule::config::url::Error;
use crate::file::submodule;
fn submodule_url(value: &str) -> Error {
let module = submodule(&format!("[submodule.a]\nurl = {value}"));
module.url("a".into()).unwrap_err()
}
#[test]
fn valid() -> crate::Result {
let module = submodule("[submodule.a]\n url = path-to-repo");
assert_eq!(module.url("a".into())?.to_bstring(), "path-to-repo");
Ok(())
}
#[test]
fn validate_upon_retrieval() {
assert!(matches!(submodule_url(""), Error::Missing { .. }));
{
let module = submodule("[submodule.a]\n url");
assert!(matches!(module.url("a".into()).unwrap_err(), Error::Missing { .. }));
}
{
let module = submodule("[submodule.a]\n");
assert!(matches!(module.url("a".into()).unwrap_err(), Error::Missing { .. }));
}
assert!(matches!(submodule_url("file://"), Error::Parse { .. }));
}
}
mod update {
use std::str::FromStr;
use gix_submodule::config::{Update, update::Error};
use crate::file::submodule;
fn submodule_update(value: &str) -> Error {
let module = submodule(&format!("[submodule.a]\nupdate = {value}"));
module.update("a".into()).unwrap_err()
}
#[test]
fn default() {
assert_eq!(Update::default(), Update::Checkout, "as defined in the docs");
}
#[test]
fn valid() -> crate::Result {
for (valid, expected) in [
("checkout", Update::Checkout),
("rebase", Update::Rebase),
("merge", Update::Merge),
("none", Update::None),
] {
let module = submodule(&format!("[submodule.a]\n update = {valid}"));
assert_eq!(module.update("a".into())?.expect("present"), expected);
}
Ok(())
}
#[test]
fn valid_in_overrides() -> crate::Result {
let mut module = submodule("[submodule.a]\n update = merge");
let repo_config = gix_config::File::from_str("[submodule.a]\n update = !dangerous")?;
let prev_names = module.names().map(ToOwned::to_owned).collect::<Vec<_>>();
module
.append_submodule_overrides(&repo_config)
.expect("the fixture fits into the backing buffer");
assert_eq!(
module.update("a".into())?.expect("present"),
Update::Command("dangerous".into()),
"overridden values are picked up and make commands possible - these are local"
);
assert_eq!(
module.names().map(ToOwned::to_owned).collect::<Vec<_>>(),
prev_names,
"Appending more configuration sections doesn't affect name listing"
);
Ok(())
}
#[test]
fn validate_upon_retrieval() {
assert!(matches!(submodule_update(""), Error::Invalid { .. }));
assert!(matches!(submodule_update("bogus"), Error::Invalid { .. }));
assert!(
matches!(
submodule_update("!dangerous"),
Error::CommandForbiddenInModulesConfiguration { .. }
),
"forbidden unless it's an override"
);
}
/// Reproducer for GHSA-f26g-jm89-4g65 and GHSA-97pq-9mjg-9fcj: `.gitmodules` may carry
/// `submodule.<name>.update = !command`, while a same-named local section without the winning
/// `update` value makes `File::update()` treat the command as trusted and expose it as
/// `Update::Command`.
#[test]
fn modules_command_is_authorized_by_unrelated_same_named_override() -> crate::Result {
let mut module = submodule("[submodule.a]\n update = !dangerous");
let repo_config = gix_config::File::from_str("[submodule.a]\n url = trusted-local-override")?;
module
.append_submodule_overrides(&repo_config)
.expect("the fixture fits into the backing buffer");
assert!(
matches!(
module.update("a".into()),
Err(Error::CommandForbiddenInModulesConfiguration {
actual,
..
}) if actual == "dangerous"
),
"a same-named local section must not authorize a command that still originates from .gitmodules"
);
Ok(())
}
}
mod fetch_recurse {
use gix_submodule::config::FetchRecurse;
use crate::file::submodule;
#[test]
fn default() {
assert_eq!(
FetchRecurse::default(),
FetchRecurse::OnDemand,
"as defined in git codebase actually"
);
}
#[test]
fn valid() -> crate::Result {
for (valid, expected) in [
("yes", FetchRecurse::Always),
("true", FetchRecurse::Always),
("", FetchRecurse::Never),
("no", FetchRecurse::Never),
("false", FetchRecurse::Never),
("on-demand", FetchRecurse::OnDemand),
] {
let module = submodule(&format!("[submodule.a]\n fetchRecurseSubmodules = {valid}"));
assert_eq!(module.fetch_recurse("a".into())?.expect("present"), expected);
}
let module = submodule("[submodule.a]\n fetchRecurseSubmodules");
assert_eq!(
module.fetch_recurse("a".into())?.expect("present"),
FetchRecurse::Always,
"no value means true, which means to always recurse"
);
Ok(())
}
#[test]
fn validate_upon_retrieval() -> crate::Result {
for invalid in ["foo", "ney", "On-demand"] {
let module = submodule(&format!("[submodule.a]\n fetchRecurseSubmodules = \"{invalid}\""));
assert!(module.fetch_recurse("a".into()).is_err());
}
Ok(())
}
}
mod ignore {
use gix_submodule::config::Ignore;
use crate::file::submodule;
#[test]
fn default() {
assert_eq!(Ignore::default(), Ignore::None, "as defined in the docs");
}
#[test]
fn valid() -> crate::Result {
for (valid, expected) in [
("all", Ignore::All),
("dirty", Ignore::Dirty),
("untracked", Ignore::Untracked),
("none", Ignore::None),
] {
let module = submodule(&format!("[submodule.a]\n ignore = {valid}"));
assert_eq!(module.ignore("a".into())?.expect("present"), expected);
}
let module = submodule("[submodule.a]\n ignore");
assert!(
module.ignore("a".into())?.is_none(),
"no value is interpreted as non-existing string, hence the caller will see None"
);
Ok(())
}
#[test]
fn validate_upon_retrieval() -> crate::Result {
for invalid in ["All", ""] {
let module = submodule(&format!("[submodule.a]\n ignore = \"{invalid}\""));
assert!(module.ignore("a".into()).is_err());
}
Ok(())
}
}
mod branch {
use gix_submodule::config::Branch;
use crate::file::submodule;
#[test]
fn valid() -> crate::Result {
for (valid, expected) in [
(".", Branch::CurrentInSuperproject),
("", Branch::Name("HEAD".into())),
("master", Branch::Name("master".into())),
("feature/a", Branch::Name("feature/a".into())),
(
"abcde12345abcde12345abcde12345abcde12345",
Branch::Name("abcde12345abcde12345abcde12345abcde12345".into()),
),
] {
let module = submodule(&format!("[submodule.a]\n branch = {valid}"));
assert_eq!(module.branch("a".into())?.expect("present"), expected);
}
let module = submodule("[submodule.a]\n branch");
assert!(
module.branch("a".into())?.is_none(),
"no value implies it's not set, but the caller will then default"
);
Ok(())
}
#[test]
fn validate_upon_retrieval() -> crate::Result {
let module = submodule("[submodule.a]\n branch = /invalid");
assert!(module.branch("a".into()).is_err());
Ok(())
}
}
#[test]
fn shallow() -> crate::Result {
let module = submodule("[submodule.a]\n shallow");
assert_eq!(
module.shallow("a".into())?,
Some(true),
"shallow is a simple boolean without anything special (yet)"
);
Ok(())
}
mod append_submodule_overrides {
use std::str::FromStr;
use crate::file::submodule;
#[test]
fn last_of_multiple_values_wins() -> crate::Result {
let mut module = submodule("[submodule.a] url = from-module");
let repo_config = gix_config::File::from_str(
"[submodule.a]\n url = a\n url = b\n ignore = x\n [submodule.a]\n url = c\n[submodule.b] url = not-relevant",
)?;
module
.append_submodule_overrides(&repo_config)
.expect("the fixture fits into the backing buffer");
Ok(())
}
}
mod baseline;