diff --git a/Cargo.lock b/Cargo.lock index d8aee88db33..083a1ea1c78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1586,7 +1586,6 @@ dependencies = [ "bstr", "bytesize", "cap", - "gix", "gix-config", "gix-path 0.10.12", "gix-ref 0.48.0", diff --git a/crate-status.md b/crate-status.md index 502583127b8..24f179e2e8e 100644 --- a/crate-status.md +++ b/crate-status.md @@ -747,7 +747,7 @@ See its [README.md](/~https://github.com/GitoxideLabs/gitoxide/blob/main/gix-lock/ * all config values as per the `gix-config-value` crate * **includeIf** * [x] `gitdir`, `gitdir/i`, and `onbranch` - * [ ] `hasconfig` + * [x] `hasconfig:remote.*.url` * [x] access values and sections by name and sub-section * [x] edit configuration in memory, non-destructively * cross-platform newline handling diff --git a/gix-config/src/file/includes/mod.rs b/gix-config/src/file/includes/mod.rs index efb76d1f5b9..434042ea9ac 100644 --- a/gix-config/src/file/includes/mod.rs +++ b/gix-config/src/file/includes/mod.rs @@ -23,11 +23,16 @@ impl File<'static> { /// times. It's recommended use is as part of a multi-step bootstrapping which needs fine-grained control, /// and unless that's given one should prefer one of the other ways of initialization that resolve includes /// at the right time. + /// + /// # Deviation + /// /// - included values are added after the _section_ that included them, not directly after the value. This is /// a deviation from how git does it, as it technically adds new value right after the include path itself, /// technically 'splitting' the section. This can only make a difference if the `include` section also has values /// which later overwrite portions of the included file, which seems unusual as these would be related to `includes`. /// We can fix this by 'splitting' the include section if needed so the included sections are put into the right place. + /// - `hasconfig:remote.*.url` will not prevent itself to include files with `[remote "name"]\nurl = x` values, but it also + /// won't match them, i.e. one cannot include something that will cause the condition to match or to always be true. pub fn resolve_includes(&mut self, options: init::Options<'_>) -> Result<(), Error> { if options.includes.max_depth == 0 { return Ok(()); @@ -38,10 +43,11 @@ impl File<'static> { } pub(crate) fn resolve(config: &mut File<'static>, buf: &mut Vec, options: init::Options<'_>) -> Result<(), Error> { - resolve_includes_recursive(config, 0, buf, options) + resolve_includes_recursive(None, config, 0, buf, options) } fn resolve_includes_recursive( + search_config: Option<&File<'static>>, target_config: &mut File<'static>, depth: u8, buf: &mut Vec, @@ -57,30 +63,34 @@ fn resolve_includes_recursive( }; } - let mut section_ids_and_include_paths = Vec::new(); - for (id, section) in target_config - .section_order - .iter() - .map(|id| (*id, &target_config.sections[id])) - { + for id in target_config.section_order.clone().into_iter() { + let section = &target_config.sections[&id]; let header = §ion.header; let header_name = header.name.as_ref(); + let mut paths = None; if header_name == "include" && header.subsection_name.is_none() { - detach_include_paths(&mut section_ids_and_include_paths, section, id); + paths = Some(gather_paths(section, id)); } else if header_name == "includeIf" { if let Some(condition) = &header.subsection_name { let target_config_path = section.meta.path.as_deref(); - if include_condition_match(condition.as_ref(), target_config_path, options.includes)? { - detach_include_paths(&mut section_ids_and_include_paths, section, id); + if include_condition_match( + condition.as_ref(), + target_config_path, + search_config.unwrap_or(target_config), + options.includes, + )? { + paths = Some(gather_paths(section, id)); } } } + if let Some(paths) = paths { + insert_includes_recursively(paths, target_config, depth, options, buf)?; + } } - - append_followed_includes_recursively(section_ids_and_include_paths, target_config, depth, options, buf) + Ok(()) } -fn append_followed_includes_recursively( +fn insert_includes_recursively( section_ids_and_include_paths: Vec<(SectionId, crate::Path<'_>)>, target_config: &mut File<'static>, depth: u8, @@ -124,30 +134,26 @@ fn append_followed_includes_recursively( init::Error::Interpolate(err) => Error::Interpolate(err), init::Error::Includes(_) => unreachable!("BUG: {:?} not possible due to no-follow options", err), })?; - resolve_includes_recursive(&mut include_config, depth + 1, buf, options)?; + resolve_includes_recursive(Some(target_config), &mut include_config, depth + 1, buf, options)?; target_config.append_or_insert(include_config, Some(section_id)); } Ok(()) } -fn detach_include_paths( - include_paths: &mut Vec<(SectionId, crate::Path<'static>)>, - section: &file::Section<'_>, - id: SectionId, -) { - include_paths.extend( - section - .body - .values("path") - .into_iter() - .map(|path| (id, crate::Path::from(Cow::Owned(path.into_owned())))), - ); +fn gather_paths(section: &file::Section<'_>, id: SectionId) -> Vec<(SectionId, crate::Path<'static>)> { + section + .body + .values("path") + .into_iter() + .map(|path| (id, crate::Path::from(Cow::Owned(path.into_owned())))) + .collect() } fn include_condition_match( condition: &BStr, target_config_path: Option<&Path>, + search_config: &File<'static>, options: Options<'_>, ) -> Result { let mut tokens = condition.splitn(2, |b| *b == b':'); @@ -170,6 +176,32 @@ fn include_condition_match( gix_glob::wildmatch::Mode::IGNORE_CASE, ), b"onbranch" => Ok(onbranch_matches(condition, options.conditional).is_some()), + b"hasconfig" => { + let mut tokens = condition.splitn(2, |b| *b == b':'); + let (key_glob, value_glob) = match (tokens.next(), tokens.next()) { + (Some(a), Some(b)) => (a, b), + _ => return Ok(false), + }; + if key_glob.as_bstr() != "remote.*.url" { + return Ok(false); + } + let Some(sections) = search_config.sections_by_name("remote") else { + return Ok(false); + }; + for remote in sections { + for url in remote.values("url") { + let glob_matches = gix_glob::wildmatch( + value_glob.as_bstr(), + url.as_ref(), + gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL, + ); + if glob_matches { + return Ok(true); + } + } + } + Ok(false) + } _ => Ok(false), } } diff --git a/gix-config/tests/Cargo.toml b/gix-config/tests/Cargo.toml index 3809ff5a137..36895eb9373 100644 --- a/gix-config/tests/Cargo.toml +++ b/gix-config/tests/Cargo.toml @@ -14,7 +14,7 @@ publish = false [[test]] name = "config" -path = "config.rs" +path = "config/mod.rs" [[test]] name = "mem" @@ -23,7 +23,6 @@ path = "mem.rs" [dev-dependencies] gix-config = { path = ".." } gix-testtools = { path = "../../tests/tools" } -gix = { path = "../../gix", default-features = false } gix-ref = { path = "../../gix-ref" } gix-path = { path = "../../gix-path" } gix-sec = { path = "../../gix-sec" } diff --git a/gix-config/tests/file/access/mod.rs b/gix-config/tests/config/file/access/mod.rs similarity index 100% rename from gix-config/tests/file/access/mod.rs rename to gix-config/tests/config/file/access/mod.rs diff --git a/gix-config/tests/file/access/mutate.rs b/gix-config/tests/config/file/access/mutate.rs similarity index 100% rename from gix-config/tests/file/access/mutate.rs rename to gix-config/tests/config/file/access/mutate.rs diff --git a/gix-config/tests/file/access/raw/mod.rs b/gix-config/tests/config/file/access/raw/mod.rs similarity index 100% rename from gix-config/tests/file/access/raw/mod.rs rename to gix-config/tests/config/file/access/raw/mod.rs diff --git a/gix-config/tests/file/access/raw/raw_multi_value.rs b/gix-config/tests/config/file/access/raw/raw_multi_value.rs similarity index 100% rename from gix-config/tests/file/access/raw/raw_multi_value.rs rename to gix-config/tests/config/file/access/raw/raw_multi_value.rs diff --git a/gix-config/tests/file/access/raw/raw_value.rs b/gix-config/tests/config/file/access/raw/raw_value.rs similarity index 100% rename from gix-config/tests/file/access/raw/raw_value.rs rename to gix-config/tests/config/file/access/raw/raw_value.rs diff --git a/gix-config/tests/file/access/raw/set_existing_raw_value.rs b/gix-config/tests/config/file/access/raw/set_existing_raw_value.rs similarity index 100% rename from gix-config/tests/file/access/raw/set_existing_raw_value.rs rename to gix-config/tests/config/file/access/raw/set_existing_raw_value.rs diff --git a/gix-config/tests/file/access/raw/set_raw_value.rs b/gix-config/tests/config/file/access/raw/set_raw_value.rs similarity index 100% rename from gix-config/tests/file/access/raw/set_raw_value.rs rename to gix-config/tests/config/file/access/raw/set_raw_value.rs diff --git a/gix-config/tests/file/access/read_only.rs b/gix-config/tests/config/file/access/read_only.rs similarity index 100% rename from gix-config/tests/file/access/read_only.rs rename to gix-config/tests/config/file/access/read_only.rs diff --git a/gix-config/tests/file/impls/mod.rs b/gix-config/tests/config/file/impls/mod.rs similarity index 100% rename from gix-config/tests/file/impls/mod.rs rename to gix-config/tests/config/file/impls/mod.rs diff --git a/gix-config/tests/file/init/comfort.rs b/gix-config/tests/config/file/init/comfort.rs similarity index 100% rename from gix-config/tests/file/init/comfort.rs rename to gix-config/tests/config/file/init/comfort.rs diff --git a/gix-config/tests/file/init/from_env.rs b/gix-config/tests/config/file/init/from_env.rs similarity index 100% rename from gix-config/tests/file/init/from_env.rs rename to gix-config/tests/config/file/init/from_env.rs diff --git a/gix-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs b/gix-config/tests/config/file/init/from_paths/includes/conditional/gitdir/mod.rs similarity index 100% rename from gix-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs rename to gix-config/tests/config/file/init/from_paths/includes/conditional/gitdir/mod.rs diff --git a/gix-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/gix-config/tests/config/file/init/from_paths/includes/conditional/gitdir/util.rs similarity index 100% rename from gix-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs rename to gix-config/tests/config/file/init/from_paths/includes/conditional/gitdir/util.rs diff --git a/gix-config/tests/config/file/init/from_paths/includes/conditional/hasconfig.rs b/gix-config/tests/config/file/init/from_paths/includes/conditional/hasconfig.rs new file mode 100644 index 00000000000..5ed6de7a030 --- /dev/null +++ b/gix-config/tests/config/file/init/from_paths/includes/conditional/hasconfig.rs @@ -0,0 +1,78 @@ +use gix_config::file::{includes, init}; +use std::path::{Path, PathBuf}; + +#[test] +fn simple() -> crate::Result { + let (config, root) = config_with_includes("basic")?; + compare_baseline(&config, "user.this", root.join("expected")); + assert_eq!(config.string("user.that"), None); + Ok(()) +} + +#[test] +fn inclusion_order() -> crate::Result { + let (config, root) = config_with_includes("inclusion-order")?; + for key in ["one", "two", "three"] { + compare_baseline(&config, format!("user.{key}"), root.join(format!("expected.{key}"))); + } + Ok(()) +} + +#[test] +fn globs() -> crate::Result { + let (config, root) = config_with_includes("globs")?; + for key in ["dss", "dse", "dsm", "ssm"] { + compare_baseline(&config, format!("user.{key}"), root.join(format!("expected.{key}"))); + } + assert_eq!(config.string("user.no"), None); + Ok(()) +} + +#[test] +fn cycle_breaker() -> crate::Result { + for name in ["cycle-breaker-direct", "cycle-breaker-indirect"] { + let (_config, _root) = config_with_includes(name)?; + } + + Ok(()) +} + +#[test] +fn no_cycle() -> crate::Result { + let (config, root) = config_with_includes("no-cycle")?; + compare_baseline(&config, "user.name", root.join("expected")); + Ok(()) +} + +fn compare_baseline(config: &gix_config::File<'static>, key: impl AsRef, expected: impl AsRef) { + let expected = expected.as_ref(); + let key = key.as_ref(); + assert_eq!( + config + .string(key) + .unwrap_or_else(|| panic!("key '{key} should be included")) + .as_ref(), + std::fs::read_to_string(expected) + .unwrap_or_else(|err| panic!("Couldn't find '{expected:?}' for reading: {err}")) + .trim(), + "baseline with git should match: '{key}' != {expected:?}" + ); +} + +fn config_with_includes(name: &str) -> crate::Result<(gix_config::File<'static>, PathBuf)> { + let root = gix_testtools::scripted_fixture_read_only_standalone("hasconfig.sh")?.join(name); + let options = init::Options { + includes: includes::Options::follow(Default::default(), Default::default()), + ..Default::default() + }; + + let config = gix_config::File::from_paths_metadata( + Some(gix_config::file::Metadata::try_from_path( + root.join("config"), + gix_config::Source::Local, + )?), + options, + )? + .expect("non-empty"); + Ok((config, root)) +} diff --git a/gix-config/tests/file/init/from_paths/includes/conditional/mod.rs b/gix-config/tests/config/file/init/from_paths/includes/conditional/mod.rs similarity index 90% rename from gix-config/tests/file/init/from_paths/includes/conditional/mod.rs rename to gix-config/tests/config/file/init/from_paths/includes/conditional/mod.rs index 4ce070bad80..ea3a4efbc70 100644 --- a/gix-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/gix-config/tests/config/file/init/from_paths/includes/conditional/mod.rs @@ -9,6 +9,7 @@ use gix_testtools::tempfile::tempdir; use crate::file::{cow_str, init::from_paths::escape_backslashes}; mod gitdir; +mod hasconfig; mod onbranch; #[test] @@ -137,18 +138,21 @@ fn options_with_git_dir(git_dir: &Path) -> init::Options<'_> { } } -fn git_init(path: impl AsRef, bare: bool) -> crate::Result { - Ok(gix::ThreadSafeRepository::init_opts( - path, - if bare { - gix::create::Kind::Bare - } else { - gix::create::Kind::WithWorktree - }, - gix::create::Options::default(), - gix::open::Options::isolated().config_overrides(["user.name=gitoxide", "user.email=gitoxide@localhost"]), - )? - .to_thread_local()) +fn git_init(dir: impl AsRef, bare: bool) -> crate::Result { + let dir = dir.as_ref(); + let mut args = vec!["init"]; + if bare { + args.push("--bare"); + } + let output = std::process::Command::new(gix_path::env::exe_invocation()) + .args(args) + .arg(dir) + .env_remove("GIT_CONFIG_COUNT") + .env_remove("XDG_CONFIG_HOME") + .output()?; + + assert!(output.status.success(), "{output:?}, {dir:?}"); + Ok(()) } fn create_symlink(from: impl AsRef, to: impl AsRef) { diff --git a/gix-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/gix-config/tests/config/file/init/from_paths/includes/conditional/onbranch.rs similarity index 79% rename from gix-config/tests/file/init/from_paths/includes/conditional/onbranch.rs rename to gix-config/tests/config/file/init/from_paths/includes/conditional/onbranch.rs index c6c2c7b2ffb..6b7e1d6042a 100644 --- a/gix-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/gix-config/tests/config/file/init/from_paths/includes/conditional/onbranch.rs @@ -6,10 +6,7 @@ use gix_config::file::{ includes::conditional, init::{self}, }; -use gix_ref::{ - transaction::{Change, PreviousValue, RefEdit}, - FullName, Target, -}; +use gix_ref::FullName; use gix_testtools::tempfile::tempdir; use crate::file::{cow_str, init::from_paths::includes::conditional::git_init}; @@ -24,7 +21,7 @@ fn literal_branch_names_match() -> Result { branch_name: "refs/heads/literal-match", expect: Value::OverrideByInclude, }, - GitEnv::new()?, + &mut GitEnv::new()?, )?; Ok(()) } @@ -37,7 +34,7 @@ fn full_ref_names_do_not_match() -> Result { branch_name: "refs/heads/simple", expect: Value::Base, }, - GitEnv::new()?, + &mut GitEnv::new()?, )?; Ok(()) } @@ -50,7 +47,7 @@ fn non_branches_never_match() -> Result { branch_name: "refs/bisect/good", expect: Value::Base, }, - GitEnv::new()?, + &mut GitEnv::new()?, )?; Ok(()) } @@ -58,21 +55,21 @@ fn non_branches_never_match() -> Result { #[test] fn patterns_ending_with_slash_match_subdirectories_recursively() -> Result { let mut env = GitEnv::new()?; - env = assert_section_value( + assert_section_value( Options { condition: "feature/b/", branch_name: "refs/heads/feature/b/start", expect: Value::OverrideByInclude, }, - env, + &mut env, )?; - env = assert_section_value( + assert_section_value( Options { condition: "feature/", branch_name: "refs/heads/feature/b/start", expect: Value::OverrideByInclude, }, - env, + &mut env, )?; assert_section_value_msg( Options { @@ -80,7 +77,7 @@ fn patterns_ending_with_slash_match_subdirectories_recursively() -> Result { branch_name: "refs/heads/feature/b/start", expect: Value::OverrideByInclude, }, - env, + &mut env, "just for good measure, we would expect branch paths to work as well".into(), )?; Ok(()) @@ -89,38 +86,38 @@ fn patterns_ending_with_slash_match_subdirectories_recursively() -> Result { #[test] fn simple_glob_patterns() -> Result { let mut env = GitEnv::new()?; - env = assert_section_value( + assert_section_value( Options { condition: "prefix*", branch_name: "refs/heads/prefixsuffix", expect: Value::OverrideByInclude, }, - env, + &mut env, )?; - env = assert_section_value_msg( + assert_section_value_msg( Options { condition: "prefix*", branch_name: "refs/heads/prefix/suffix", expect: Value::Base, }, - env, + &mut env, "single-stars do not cross component boundaries".into(), )?; - env = assert_section_value( + assert_section_value( Options { condition: "*suffix", branch_name: "refs/heads/prefixsuffix", expect: Value::OverrideByInclude, }, - env, + &mut env, )?; - env = assert_section_value( + assert_section_value( Options { condition: "*/suffix", branch_name: "refs/heads/prefix/suffix", expect: Value::OverrideByInclude, }, - env, + &mut env, )?; assert_section_value_msg( Options { @@ -128,7 +125,7 @@ fn simple_glob_patterns() -> Result { branch_name: "refs/heads/prefix/suffix", expect: Value::Base, }, - env, + &mut env, "single-stars do not cross component boundaries".into(), )?; Ok(()) @@ -137,13 +134,13 @@ fn simple_glob_patterns() -> Result { #[test] fn simple_globs_do_not_cross_component_boundary() -> Result { let mut env = GitEnv::new()?; - env = assert_section_value( + assert_section_value( Options { condition: "feature/*/start", branch_name: "refs/heads/feature/a/start", expect: Value::OverrideByInclude, }, - env, + &mut env, )?; assert_section_value_msg( Options { @@ -151,7 +148,7 @@ fn simple_globs_do_not_cross_component_boundary() -> Result { branch_name: "refs/heads/feature/a/b/start", expect: Value::Base, }, - env, + &mut env, "path matching would never match 'a/b' as it cannot cross /".into(), )?; Ok(()) @@ -165,7 +162,7 @@ fn double_star_globs_cross_component_boundaries() -> Result { branch_name: "refs/heads/feature/a/b/start", expect: Value::OverrideByInclude, }, - GitEnv::new()?, + &mut GitEnv::new()?, )?; Ok(()) } @@ -177,15 +174,14 @@ enum Value { #[derive(Debug)] struct GitEnv { - repo: gix::Repository, dir: gix_testtools::tempfile::TempDir, } impl GitEnv { fn new() -> crate::Result { let dir = tempdir()?; - let repo = git_init(dir.path(), true)?; - Ok(GitEnv { repo, dir }) + git_init(dir.path(), true)?; + Ok(GitEnv { dir }) } } @@ -195,7 +191,7 @@ struct Options<'a> { expect: Value, } -fn assert_section_value(opts: Options, env: GitEnv) -> crate::Result { +fn assert_section_value(opts: Options, env: &mut GitEnv) -> crate::Result { assert_section_value_msg(opts, env, None) } @@ -205,9 +201,9 @@ fn assert_section_value_msg( branch_name, expect, }: Options, - GitEnv { repo, dir }: GitEnv, + GitEnv { dir }: &mut GitEnv, message: Option<&str>, -) -> crate::Result { +) -> crate::Result<()> { let root_config = dir.path().join("config"); let included_config = dir.path().join("include.config"); @@ -264,36 +260,23 @@ value = branch-override-by-include "the base value is overridden by an included file because the condition matches", }, message, - dir.into_path() + { + let dir = std::mem::replace( + dir, + gix_testtools::tempfile::TempDir::new().expect("substitute can be created"), + ); + dir.into_path() + } ); - repo.refs - .transaction() - .prepare( - Some(RefEdit { - name: "HEAD".try_into()?, - change: Change::Update { - log: Default::default(), - expected: PreviousValue::Any, - new: Target::Symbolic(branch_name), - }, - deref: false, - }), - gix::lock::acquire::Fail::Immediately, - gix::lock::acquire::Fail::Immediately, - )? - .commit(repo.committer().transpose()?)?; - - let dir = assure_git_agrees(expect, dir)?; - Ok(GitEnv { repo, dir }) + std::fs::write(dir.path().join("HEAD"), format!("ref: {}", branch_name.as_bstr()))?; + assure_git_agrees(expect, dir)?; + Ok(()) } -fn assure_git_agrees( - expected: Value, - dir: gix_testtools::tempfile::TempDir, -) -> crate::Result { +fn assure_git_agrees(expected: Value, dir: &mut gix_testtools::tempfile::TempDir) -> crate::Result { let git_dir = dir.path(); - let output = std::process::Command::new("git") + let output = std::process::Command::new(gix_path::env::exe_invocation()) .args(["config", "--get", "section.value"]) .env("GIT_DIR", git_dir) .env("HOME", git_dir) @@ -302,11 +285,18 @@ fn assure_git_agrees( .current_dir(git_dir) .output()?; + let mut keep_dir_on_disk = || { + let dir = std::mem::replace( + dir, + gix_testtools::tempfile::TempDir::new().expect("substitute can be created"), + ); + dir.into_path() + }; assert!( output.status.success(), "{:?}, {:?} for debugging", output, - dir.into_path() + keep_dir_on_disk() ); let git_output: BString = output.stdout.trim_end().into(); assert_eq!( @@ -316,7 +306,7 @@ fn assure_git_agrees( Value::OverrideByInclude => "branch-override-by-include", }, "git disagrees with gix-config, {:?} for debugging", - dir.into_path() + keep_dir_on_disk() ); - Ok(dir) + Ok(()) } diff --git a/gix-config/tests/file/init/from_paths/includes/unconditional.rs b/gix-config/tests/config/file/init/from_paths/includes/unconditional.rs similarity index 100% rename from gix-config/tests/file/init/from_paths/includes/unconditional.rs rename to gix-config/tests/config/file/init/from_paths/includes/unconditional.rs diff --git a/gix-config/tests/file/init/from_paths/mod.rs b/gix-config/tests/config/file/init/from_paths/mod.rs similarity index 100% rename from gix-config/tests/file/init/from_paths/mod.rs rename to gix-config/tests/config/file/init/from_paths/mod.rs diff --git a/gix-config/tests/file/init/from_str.rs b/gix-config/tests/config/file/init/from_str.rs similarity index 100% rename from gix-config/tests/file/init/from_str.rs rename to gix-config/tests/config/file/init/from_str.rs diff --git a/gix-config/tests/file/init/mod.rs b/gix-config/tests/config/file/init/mod.rs similarity index 100% rename from gix-config/tests/file/init/mod.rs rename to gix-config/tests/config/file/init/mod.rs diff --git a/gix-config/tests/file/mod.rs b/gix-config/tests/config/file/mod.rs similarity index 96% rename from gix-config/tests/file/mod.rs rename to gix-config/tests/config/file/mod.rs index e09f4480e85..1022f6286cc 100644 --- a/gix-config/tests/file/mod.rs +++ b/gix-config/tests/config/file/mod.rs @@ -30,7 +30,7 @@ mod open { #[test] fn fuzzed_stackoverflow() { let file = File::from_bytes_no_includes( - include_bytes!("../fixtures/fuzzed/stackoverflow-01.config"), + include_bytes!("../../fixtures/fuzzed/stackoverflow-01.config"), gix_config::file::Metadata::default(), Default::default(), ) diff --git a/gix-config/tests/file/mutable/mod.rs b/gix-config/tests/config/file/mutable/mod.rs similarity index 100% rename from gix-config/tests/file/mutable/mod.rs rename to gix-config/tests/config/file/mutable/mod.rs diff --git a/gix-config/tests/file/mutable/multi_value.rs b/gix-config/tests/config/file/mutable/multi_value.rs similarity index 100% rename from gix-config/tests/file/mutable/multi_value.rs rename to gix-config/tests/config/file/mutable/multi_value.rs diff --git a/gix-config/tests/file/mutable/section.rs b/gix-config/tests/config/file/mutable/section.rs similarity index 100% rename from gix-config/tests/file/mutable/section.rs rename to gix-config/tests/config/file/mutable/section.rs diff --git a/gix-config/tests/file/mutable/value.rs b/gix-config/tests/config/file/mutable/value.rs similarity index 100% rename from gix-config/tests/file/mutable/value.rs rename to gix-config/tests/config/file/mutable/value.rs diff --git a/gix-config/tests/file/resolve_includes.rs b/gix-config/tests/config/file/resolve_includes.rs similarity index 100% rename from gix-config/tests/file/resolve_includes.rs rename to gix-config/tests/config/file/resolve_includes.rs diff --git a/gix-config/tests/file/write.rs b/gix-config/tests/config/file/write.rs similarity index 100% rename from gix-config/tests/file/write.rs rename to gix-config/tests/config/file/write.rs diff --git a/gix-config/tests/key/mod.rs b/gix-config/tests/config/key/mod.rs similarity index 100% rename from gix-config/tests/key/mod.rs rename to gix-config/tests/config/key/mod.rs diff --git a/gix-config/tests/config.rs b/gix-config/tests/config/mod.rs similarity index 90% rename from gix-config/tests/config.rs rename to gix-config/tests/config/mod.rs index 3efeb7c63c9..2782af4095a 100644 --- a/gix-config/tests/config.rs +++ b/gix-config/tests/config/mod.rs @@ -2,7 +2,6 @@ pub use gix_testtools::Result; mod file; mod key; -mod mem; mod parse; mod source; mod value; diff --git a/gix-config/tests/parse/error.rs b/gix-config/tests/config/parse/error.rs similarity index 100% rename from gix-config/tests/parse/error.rs rename to gix-config/tests/config/parse/error.rs diff --git a/gix-config/tests/parse/from_bytes.rs b/gix-config/tests/config/parse/from_bytes.rs similarity index 97% rename from gix-config/tests/parse/from_bytes.rs rename to gix-config/tests/config/parse/from_bytes.rs index 736ddfedca0..fd25f84d0e4 100644 --- a/gix-config/tests/parse/from_bytes.rs +++ b/gix-config/tests/config/parse/from_bytes.rs @@ -8,7 +8,7 @@ fn fuzz() { ); assert!( Events::from_str(include_str!( - "../fixtures/clusterfuzz-testcase-minimized-gix-config-parse-6431708583690240" + "../../fixtures/clusterfuzz-testcase-minimized-gix-config-parse-6431708583690240" )) .is_err(), "works without hanging - these 400kb take 10s in debug mode right now, but just as long in release mode. With nom all tests ran in below 1s in debug mode" diff --git a/gix-config/tests/parse/mod.rs b/gix-config/tests/config/parse/mod.rs similarity index 100% rename from gix-config/tests/parse/mod.rs rename to gix-config/tests/config/parse/mod.rs diff --git a/gix-config/tests/parse/section.rs b/gix-config/tests/config/parse/section.rs similarity index 100% rename from gix-config/tests/parse/section.rs rename to gix-config/tests/config/parse/section.rs diff --git a/gix-config/tests/source/mod.rs b/gix-config/tests/config/source/mod.rs similarity index 100% rename from gix-config/tests/source/mod.rs rename to gix-config/tests/config/source/mod.rs diff --git a/gix-config/tests/value/mod.rs b/gix-config/tests/config/value/mod.rs similarity index 100% rename from gix-config/tests/value/mod.rs rename to gix-config/tests/config/value/mod.rs diff --git a/gix-config/tests/value/normalize.rs b/gix-config/tests/config/value/normalize.rs similarity index 100% rename from gix-config/tests/value/normalize.rs rename to gix-config/tests/config/value/normalize.rs diff --git a/gix-config/tests/fixtures/generated-archives/hasconfig.tar b/gix-config/tests/fixtures/generated-archives/hasconfig.tar new file mode 100644 index 00000000000..adfe35d3f84 Binary files /dev/null and b/gix-config/tests/fixtures/generated-archives/hasconfig.tar differ diff --git a/gix-config/tests/fixtures/hasconfig.sh b/gix-config/tests/fixtures/hasconfig.sh new file mode 100755 index 00000000000..39f26534c17 --- /dev/null +++ b/gix-config/tests/fixtures/hasconfig.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +set -eu -o pipefail + +git init --bare basic +(cd basic + cat >include-this <<-\EOF + [user] + this = this-is-included +EOF + + cat >dont-include-that <<-\EOF + [user] + that = that-is-not-included +EOF + + cat >>config <<-EOF + [includeIf "hasconfig:remote.*.url:foourl"] + path = "include-this" + [includeIf "hasconfig:remote.*.url:barurl"] + path = "dont-include-that" + [remote "foo"] + url = foourl +EOF + + git config --get user.this >expected +) + +git init --bare inclusion-order +(cd inclusion-order + cat >include-two-three <<-\EOF + [user] + two = included-config + three = included-config +EOF + cat >include-four <<-\EOF + [user] + four = included-config +EOF + cat >include-five <<-\EOF + [user] + five = included-config +EOF + cat >indirect <<-\EOF + [includeIf "hasconfig:remote.*.url:early"] + path = "include-five" +EOF + cat >>config <<-EOF + [remote "foo"] + url = before + [remote "other"] + url = early + [user] + one = main-config + [includeIf "hasconfig:remote.*.url:before"] + path = "include-two-three" + [includeIf "hasconfig:remote.*.url:after"] + path = "include-four" + [user] + three = main-config + five = main-config + [remote "bar"] + url = after + [include] + path = "indirect" +EOF + git config --get user.one >expected.one + git config --get user.two >expected.two + git config --get user.three >expected.three + git config --get user.four >expected.four + git config --get user.five >expected.five +) + +git init --bare globs +(cd globs + printf "[user]\ndss = yes\n" >double-star-start + printf "[user]\ndse = yes\n" >double-star-end + printf "[user]\ndsm = yes\n" >double-star-middle + printf "[user]\nssm = yes\n" >single-star-middle + printf "[user]\nno = no\n" >no + + cat >>config <<-EOF + [remote "foo"] + url = https://foo/bar/baz + [includeIf "hasconfig:remote.*.url:**/baz"] + path = "double-star-start" + [includeIf "hasconfig:remote.*.url:**/nomatch"] + path = "no" + [includeIf "hasconfig:remote.*.url:https:/**"] + path = "double-star-end" + [includeIf "hasconfig:remote.*.url:nomatch:/**"] + path = "no" + [includeIf "hasconfig:remote.*.url:https:/**/baz"] + path = "double-star-middle" + [includeIf "hasconfig:remote.*.url:https:/**/nomatch"] + path = "no" + [includeIf "hasconfig:remote.*.url:https://*/bar/baz"] + path = "single-star-middle" + [includeIf "hasconfig:remote.*.url:https://*/baz"] + path = "no" +EOF + + git config --get user.dss > expected.dss + git config --get user.dse > expected.dse + git config --get user.dsm > expected.dsm + git config --get user.ssm > expected.ssm +) + + +git init --bare cycle-breaker-direct +(cd cycle-breaker-direct + cat >include-with-url <<-\EOF + [remote "bar"] + url = barurl +EOF + cat >>config <<-EOF + [include] + path = "include-with-url" + [includeIf "hasconfig:remote.*.url:foourl"] + path = "include-with-url" + [include] + path = "include-with-url" +EOF +) + +git init --bare cycle-breaker-indirect +(cd cycle-breaker-indirect + cat >include-with-url <<-\EOF + [include] + path = indirect +EOF + cat >indirect <<-\EOF + [remote "bar"] + url = barurl +EOF + cat >>config <<-EOF + [include] + path = "include-with-url" + [includeIf "hasconfig:remote.*.url:foourl"] + path = "include-with-url" + [include] + path = "include-with-url" +EOF +) + +git init --bare no-cycle +(cd no-cycle + cat >include-with-url <<-\EOF + [user] + name = "works" +EOF + cat >remote <<-\EOF + [remote "bar"] + url = barurl +EOF + cat >>config <<-EOF + [include] + path = "remote" + [includeIf "hasconfig:remote.*.url:barurl"] + path = "include-with-url" + [include] + path = "remote" +EOF + git config --get user.name > expected +) diff --git a/gix/tests/fixtures/generated-archives/make_config_repos.tar b/gix/tests/fixtures/generated-archives/make_config_repos.tar index 77529fdf5d1..426d7c022f5 100644 Binary files a/gix/tests/fixtures/generated-archives/make_config_repos.tar and b/gix/tests/fixtures/generated-archives/make_config_repos.tar differ diff --git a/gix/tests/fixtures/make_config_repos.sh b/gix/tests/fixtures/make_config_repos.sh index be8b3f75706..e10354145d1 100755 --- a/gix/tests/fixtures/make_config_repos.sh +++ b/gix/tests/fixtures/make_config_repos.sh @@ -175,3 +175,20 @@ git init ssl-no-verify-enabled git config http.sslVerify true git config gitoxide.http.sslNoVerify true ) + + +git init --bare with-hasconfig +(cd with-hasconfig + cat >include-with-url <<-\EOF + [user] + name = "works" + email = "works@example.com" +EOF + cat >system.config <<-\EOF + [includeIf "hasconfig:remote.*.url:anyurl"] + path = "include-with-url" +EOF + + echo $'[remote "any"]\n\turl=anyurl' >>config + +) diff --git a/gix/tests/gix/repository/config/identity.rs b/gix/tests/gix/repository/config/identity.rs index ddbd0eb9607..4db71402619 100644 --- a/gix/tests/gix/repository/config/identity.rs +++ b/gix/tests/gix/repository/config/identity.rs @@ -1,10 +1,24 @@ -use std::path::Path; - +use crate::named_repo; +use crate::util::named_subrepo_opts; use gix_sec::Permission; use gix_testtools::Env; use serial_test::serial; +use std::path::Path; -use crate::named_repo; +#[test] +#[serial] +fn author_included_by_hasconfig() -> crate::Result { + let repo = named_subrepo_opts("make_config_repos.sh", "with-hasconfig", gix::open::Options::isolated())?; + let _env = Env::new().set( + "GIT_CONFIG_SYSTEM", + repo.git_dir().join("system.config").display().to_string(), + ); + let repo = gix::open_opts(repo.git_dir(), allow_system_options(repo.open_options().clone()))?; + let author = repo.author().expect("set in system config via include")?; + assert_eq!(author.name, "works"); + assert_eq!(author.email, "works@example.com"); + Ok(()) +} #[test] #[serial] @@ -29,17 +43,7 @@ fn author_and_committer_and_fallback() -> crate::Result { .set("GIT_CONFIG_VALUE_0", work_dir.join("c.config").display().to_string()); let repo = gix::open_opts( repo.git_dir(), - repo.open_options() - .clone() - .with(trust) - .permissions(gix::open::Permissions { - env: gix::open::permissions::Environment { - xdg_config_home: Permission::Deny, - home: Permission::Deny, - ..gix::open::permissions::Environment::all() - }, - ..Default::default() - }), + allow_system_options(repo.open_options().clone().with(trust)), )?; assert_eq!( @@ -147,18 +151,12 @@ fn author_from_different_config_sections() -> crate::Result { let repo = gix::open_opts( repo.git_dir(), - repo.open_options() - .clone() - .config_overrides(None::<&str>) - .with(gix_sec::Trust::Full) - .permissions(gix::open::Permissions { - env: gix::open::permissions::Environment { - xdg_config_home: Permission::Deny, - home: Permission::Deny, - ..gix::open::permissions::Environment::all() - }, - ..Default::default() - }), + allow_system_options( + repo.open_options() + .clone() + .config_overrides(None::<&str>) + .with(gix_sec::Trust::Full), + ), )?; assert_eq!( @@ -191,3 +189,14 @@ fn author_from_different_config_sections() -> crate::Result { ); Ok(()) } + +fn allow_system_options(opts: gix::open::Options) -> gix::open::Options { + opts.permissions(gix::open::Permissions { + env: gix::open::permissions::Environment { + xdg_config_home: Permission::Deny, + home: Permission::Deny, + ..gix::open::permissions::Environment::all() + }, + ..Default::default() + }) +}