use std::{fs, path::Path, str::FromStr}; use git_config::{ file::{includes, init}, path, File, }; use tempfile::tempdir; use crate::file::{cow_str, init::from_paths::escape_backslashes}; mod gitdir; mod onbranch; #[test] fn include_and_includeif_correct_inclusion_order_and_delayed_resolve_include() -> crate::Result { let dir = tempdir()?; let config_path = dir.path().join("root"); let first_include_path = dir.path().join("first-incl"); let second_include_path = dir.path().join("second-incl"); let include_if_path = dir.path().join("incl-if"); fs::write( first_include_path.as_path(), " ; first include beginning [section] value = first-incl-path # first include end no nl", )?; fs::write( second_include_path.as_path(), "; second include beginning [section] value = second-incl-path ; post value comment # second include end ", )?; fs::write( include_if_path.as_path(), " # includeIf beginning [section] value = incl-if-path ; include if end no nl", )?; let root_config = format!( r#" ; root beginning # root pre base [section] value = base # base comment ; root post base [include] path = {} path = {} ; paths are multi-values # root past first include [section] value = base-past-first-include # root before include-if no-nl [includeIf "gitdir:root/"] path = {} [section] value = base-past-includeIf [include] path = {} # root past last include [section] value = base-past-second-include ; root last include"#, escape_backslashes(&first_include_path), escape_backslashes(&first_include_path), escape_backslashes(&include_if_path), escape_backslashes(&second_include_path), ); fs::write(config_path.as_path(), &root_config)?; let dir = config_path.join(".git"); for delayed_resolve in [false, true] { let meta = git_config::file::Metadata::try_from_path(&config_path, git_config::Source::Api)?; let options = options_with_git_dir(&dir); let config = if delayed_resolve { let mut config = File::from_bytes_owned(&mut root_config.as_bytes().into(), meta, Default::default())?; config.resolve_includes(options)?; config } else { File::from_paths_metadata(Some(meta), options)?.expect("non-empty") }; assert_eq!( config.strings("section", None, "value"), Some(vec![ cow_str("base"), cow_str("first-incl-path"), cow_str("first-incl-path"), cow_str("base-past-first-include"), cow_str("incl-if-path"), cow_str("base-past-includeIf"), cow_str("second-incl-path"), cow_str("base-past-second-include"), ]), "include order isn't changed also in relation to the root configuratino, delayed_resolve = {}", delayed_resolve, ); assert_eq!(config.sections().count(), 11); let config_string = config.to_string(); let deserialized = File::from_str(&config_string)?; assert_eq!(config, config, "equality comparisons work"); assert_eq!( deserialized.sections().count(), config.sections().count(), "sections must match to have a chance for equality" ); assert_eq!(config, deserialized, "we can round-trip the information at least"); assert_eq!( deserialized.to_string(), config_string, "serialization works exactly as before" ); } Ok(()) } fn options_with_git_dir(git_dir: &Path) -> init::Options<'_> { init::Options { includes: includes::Options::follow( path::interpolate::Context { home_dir: Some(git_dir.parent().unwrap()), ..Default::default() }, includes::conditional::Context { git_dir: Some(git_dir), ..Default::default() }, ), ..Default::default() } } fn create_symlink(from: impl AsRef, to: impl AsRef) { std::fs::create_dir_all(from.as_ref().parent().unwrap()).unwrap(); #[cfg(not(windows))] std::os::unix::fs::symlink(to, from).unwrap(); #[cfg(windows)] std::os::windows::fs::symlink_file(to, from).unwrap(); }