| 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; |
| |
| use bstr::{BStr, ByteSlice}; |
| |
| fn multi_modules() -> crate::Result<gix_submodule::File> { |
| let modules = gix_testtools::scripted_fixture_read_only("basic.sh")? |
| .join("multiple") |
| .join(".gitmodules"); |
| Ok(gix_submodule::File::from_bytes( |
| std::fs::read(&modules)?.as_slice(), |
| modules, |
| &Default::default(), |
| )?) |
| } |
| |
| fn assume_valid_active_state<'a>( |
| module: &'a gix_submodule::File, |
| config: &'a gix_config::File<'static>, |
| 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<'static>, |
| 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(module, config, name, &mut attributes) |
| .expect("valid"), |
| ) |
| }) |
| .collect()) |
| } |
| |
| #[test] |
| fn without_any_additional_settings_all_are_active_if_they_have_a_url() -> crate::Result { |
| let module = multi_modules()?; |
| assert_eq!( |
| assume_valid_active_state(&module, &Default::default(), Default::default())?, |
| &[ |
| ("submodule", true), |
| ("a/b", true), |
| (".a/..c", true), |
| ("a/d\\", true), |
| ("a\\e", true) |
| ] |
| ); |
| 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[submodule \"a/b\"]\n active = false" |
| )?, |
| Default::default() |
| )?, |
| &[ |
| ("submodule", false), |
| ("a/b", false), |
| (".a/..c", true), |
| ("a/d\\", true), |
| ("a\\e", true) |
| ] |
| ); |
| 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), |
| ("a/d\\", false), |
| ("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), |
| ("a/d\\", false), |
| ("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), |
| ("a/d\\", true), |
| ("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())?.as_ref(), "relative/path/submodule"); |
| Ok(()) |
| } |
| |
| #[test] |
| fn validate_upon_retrieval() { |
| assert!(matches!( |
| submodule_path(if cfg!(windows) { |
| "c:\\\\hello" |
| } else { |
| "/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::Error, Update}; |
| |
| 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); |
| |
| 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" |
| ); |
| } |
| } |
| |
| 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); |
| Ok(()) |
| } |
| } |
| |
| mod baseline; |