From ba0d36cb2de1be6e8c4332d37f796393ccc07b15 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Fri, 8 Jan 2021 08:09:52 -0500 Subject: Parse GIT_CONFIG_PARAMETERS When git is invoked as `git -c aaa.bbb=ccc -c ddd.eee=fff` then git sets the env var GIT_CONFIG_PARAMETERS containing the changed config entries, so that child processes can honor them. libgit2 doesn't yet honor the env var: see https://github.com/libgit2/libgit2/issues/3854. Fixes #493 Fixes #307 Ref https://github.com/dandavison/magit-delta/issues/13 --- src/git_config/git_config.rs | 99 +++++++++++++++++++++++-- src/options/get.rs | 167 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 5 deletions(-) diff --git a/src/git_config/git_config.rs b/src/git_config/git_config.rs index ab831b31..96209603 100644 --- a/src/git_config/git_config.rs +++ b/src/git_config/git_config.rs @@ -1,9 +1,15 @@ +use regex::Regex; +use std::collections::HashMap; +use std::env; #[cfg(test)] use std::path::Path; use std::process; +use lazy_static::lazy_static; + pub struct GitConfig { config: git2::Config, + config_from_env_var: HashMap, pub enabled: bool, pub repo: Option, } @@ -26,6 +32,7 @@ impl GitConfig { }); Some(Self { config, + config_from_env_var: parse_config_from_env_var(), repo, enabled: true, }) @@ -38,6 +45,7 @@ impl GitConfig { pub fn from_path(path: &Path) -> Self { Self { config: git2::Config::open(path).unwrap(), + config_from_env_var: parse_config_from_env_var(), repo: None, enabled: true, } @@ -55,6 +63,26 @@ impl GitConfig { } } +fn parse_config_from_env_var() -> HashMap { + if let Ok(s) = env::var("GIT_CONFIG_PARAMETERS") { + parse_config_from_env_var_value(&s) + } else { + HashMap::new() + } +} + +lazy_static! { + static ref GIT_CONFIG_PARAMETERS_REGEX: Regex = + Regex::new(r"'(delta\.[a-z-]+)=([^']+)'").unwrap(); +} + +fn parse_config_from_env_var_value(s: &str) -> HashMap { + GIT_CONFIG_PARAMETERS_REGEX + .captures_iter(s) + .map(|captures| (captures[1].to_string(), captures[2].to_string())) + .collect() +} + pub trait GitConfigGet { fn git_config_get(key: &str, git_config: &GitConfig) -> Option where @@ -63,27 +91,42 @@ pub trait GitConfigGet { impl GitConfigGet for String { fn git_config_get(key: &str, git_config: &GitConfig) -> Option { - git_config.config.get_string(key).ok() + match git_config.config_from_env_var.get(key) { + Some(val) => Some(val.to_string()), + None => git_config.config.get_string(key).ok(), + } } } impl GitConfigGet for Option { fn git_config_get(key: &str, git_config: &GitConfig) -> Option { - match git_config.config.get_string(key) { - Ok(value) => Some(Some(value)), - _ => None, + match git_config.config_from_env_var.get(key) { + Some(val) => Some(Some(val.to_string())), + None => match git_config.config.get_string(key) { + Ok(val) => Some(Some(val)), + _ => None, + }, } } } impl GitConfigGet for bool { fn git_config_get(key: &str, git_config: &GitConfig) -> Option { - git_config.config.get_bool(key).ok() + match git_config.config_from_env_var.get(key).map(|s| s.as_str()) { + Some("true") => Some(true), + Some("false") => Some(false), + _ => git_config.config.get_bool(key).ok(), + } } } impl GitConfigGet for usize { fn git_config_get(key: &str, git_config: &GitConfig) -> Option { + if let Some(s) = git_config.config_from_env_var.get(key) { + if let Ok(n) = s.parse::() { + return Some(n); + } + } match git_config.config.get_i64(key) { Ok(value) => Some(value as usize), _ => None, @@ -93,9 +136,55 @@ impl GitConfigGet for usize { impl GitConfigGet for f64 { fn git_config_get(key: &str, git_config: &GitConfig) -> Option { + if let Some(s) = git_config.config_from_env_var.get(key) { + if let Ok(n) = s.parse::() { + return Some(n); + } + } match git_config.config.get_string(key) { Ok(value) => value.parse::().ok(), _ => None, } } } + +#[cfg(test)] +mod tests { + + use super::parse_config_from_env_var_value; + + #[test] + fn test_parse_config_from_env_var_value() { + // To generate test cases, use git -c ... with + // [core] + // pager = env | grep GIT_CONFIG_PARAMETERS + + let config = parse_config_from_env_var_value("'user.name=xxx'"); + assert!(config.is_empty()); + + let config = parse_config_from_env_var_value("'delta.plus-style=green'"); + assert_eq!(config["delta.plus-style"], "green"); + + let config = parse_config_from_env_var_value( + r##"'user.name=xxx' 'delta.hunk-header-line-number-style=red "#067a00"'"##, + ); + assert_eq!( + config["delta.hunk-header-line-number-style"], + r##"red "#067a00""## + ); + + let config = + parse_config_from_env_var_value(r##"'user.name=xxx' 'delta.side-by-side=false'"##); + assert_eq!(config["delta.side-by-side"], "false"); + + let config = parse_config_from_env_var_value( + r##"'delta.plus-style=green' 'delta.side-by-side=false' 'delta.hunk-header-line-number-style=red "#067a00"'"##, + ); + assert_eq!(config["delta.plus-style"], "green"); + assert_eq!(config["delta.side-by-side"], "false"); + assert_eq!( + config["delta.hunk-header-line-number-style"], + r##"red "#067a00""## + ); + } +} diff --git a/src/options/get.rs b/src/options/get.rs index e29b8cdf..f19c3293 100644 --- a/src/options/get.rs +++ b/src/options/get.rs @@ -109,3 +109,170 @@ impl GetOptionValue for String {} impl GetOptionValue for bool {} impl GetOptionValue for f64 {} impl GetOptionValue for usize {} + +#[cfg(test)] +pub mod tests { + use std::env; + use std::fs::remove_file; + + use crate::tests::integration_test_utils::integration_test_utils; + + #[test] + fn test_simple_string_env_var_overrides_git_config() { + let git_config_contents = b" +[delta] + plus-style = blue +"; + let git_config_path = "delta__test_simple_string_env_var_overrides_git_config.gitconfig"; + + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.plus_style, "blue"); + + env::set_var("GIT_CONFIG_PARAMETERS", "'delta.plus-style=green'"); + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.plus_style, "green"); + + remove_file(git_config_path).unwrap(); + } + + #[test] + fn test_complex_string_env_var_overrides_git_config() { + let git_config_contents = br##" +[delta] + minus-style = red bold ul "#ffeeee" +"##; + let git_config_path = "delta__test_complex_string_env_var_overrides_git_config.gitconfig"; + + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.minus_style, r##"red bold ul #ffeeee"##); + + env::set_var( + "GIT_CONFIG_PARAMETERS", + r##"'delta.minus-style=magenta italic ol "#aabbcc"'"##, + ); + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.minus_style, r##"magenta italic ol "#aabbcc""##,); + + remove_file(git_config_path).unwrap(); + } + + #[test] + fn test_option_string_env_var_overrides_git_config() { + let git_config_contents = b" +[delta] + plus-style = blue +"; + let git_config_path = "delta__test_option_string_env_var_overrides_git_config.gitconfig"; + + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.plus_style, "blue"); + + env::set_var("GIT_CONFIG_PARAMETERS", "'delta.plus-style=green'"); + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.plus_style, "green"); + + remove_file(git_config_path).unwrap(); + } + + #[test] + fn test_bool_env_var_overrides_git_config() { + let git_config_contents = b" +[delta] + side-by-side = true +"; + let git_config_path = "delta__test_bool_env_var_overrides_git_config.gitconfig"; + + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.side_by_side, true); + + env::set_var("GIT_CONFIG_PARAMETERS", "'delta.side-by-side=false'"); + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.side_by_side, false); + + remove_file(git_config_path).unwrap(); + } + + #[test] + fn test_int_env_var_overrides_git_config() { + let git_config_contents = b" +[delta] + max-line-length = 1 +"; + let git_config_path = "delta__test_int_env_var_overrides_git_config.gitconfig"; + + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.max_line_length, 1); + + env::set_var("GIT_CONFIG_PARAMETERS", "'delta.max-line-length=2'"); + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.max_line_length, 2); + + remove_file(git_config_path).unwrap(); + } + + #[test] + fn test_float_env_var_overrides_git_config() { + let git_config_contents = b" +[delta] + max-line-distance = 0.6 +"; + let git_config_path = "delta__test_float_env_var_overrides_git_config.gitconfig"; + + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.max_line_distance, 0.6); + + env::set_var("GIT_CONFIG_PARAMETERS", "'delta.max-line-distance=0.7'"); + let opt = integration_test_utils::make_options_from_args_and_git_config( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.max_line_distance, 0.7); + + remove_file(git_config_path).unwrap(); + } +} -- cgit v1.2.3