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 From dc267979a46caee4d79ed2e3d17af9bd513c4e39 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Fri, 8 Jan 2021 10:33:55 -0500 Subject: Prevent tests setting env vars from affecting other tests --- src/git_config/git_config.rs | 8 +++++-- src/options/get.rs | 46 ++++++++++++++++++------------------- src/tests/integration_test_utils.rs | 23 ++++++++++++++++--- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/git_config/git_config.rs b/src/git_config/git_config.rs index 96209603..d95a6b1a 100644 --- a/src/git_config/git_config.rs +++ b/src/git_config/git_config.rs @@ -42,10 +42,14 @@ impl GitConfig { } #[cfg(test)] - pub fn from_path(path: &Path) -> Self { + pub fn from_path(path: &Path, honor_env_var: bool) -> Self { Self { config: git2::Config::open(path).unwrap(), - config_from_env_var: parse_config_from_env_var(), + config_from_env_var: if honor_env_var { + parse_config_from_env_var() + } else { + HashMap::new() + }, repo: None, enabled: true, } diff --git a/src/options/get.rs b/src/options/get.rs index f19c3293..a36987ca 100644 --- a/src/options/get.rs +++ b/src/options/get.rs @@ -117,8 +117,13 @@ pub mod tests { use crate::tests::integration_test_utils::integration_test_utils; + // TODO: the followig tests are collapsed into one since they all set the same env var and thus + // could affect each other if allowed to run concurrently. + #[test] - fn test_simple_string_env_var_overrides_git_config() { + fn test_env_var_overrides_git_config() { + // ---------------------------------------------------------------------------------------- + // simple string let git_config_contents = b" [delta] plus-style = blue @@ -133,7 +138,7 @@ pub mod tests { 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( + let opt = integration_test_utils::make_options_from_args_and_git_config_honoring_env_var( &[], Some(git_config_contents), Some(git_config_path), @@ -141,10 +146,9 @@ pub mod tests { assert_eq!(opt.plus_style, "green"); remove_file(git_config_path).unwrap(); - } - #[test] - fn test_complex_string_env_var_overrides_git_config() { + // ---------------------------------------------------------------------------------------- + // complex string let git_config_contents = br##" [delta] minus-style = red bold ul "#ffeeee" @@ -162,7 +166,7 @@ pub mod tests { "GIT_CONFIG_PARAMETERS", r##"'delta.minus-style=magenta italic ol "#aabbcc"'"##, ); - let opt = integration_test_utils::make_options_from_args_and_git_config( + let opt = integration_test_utils::make_options_from_args_and_git_config_honoring_env_var( &[], Some(git_config_contents), Some(git_config_path), @@ -170,10 +174,9 @@ pub mod tests { 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() { + // ---------------------------------------------------------------------------------------- + // option string let git_config_contents = b" [delta] plus-style = blue @@ -188,7 +191,7 @@ pub mod tests { 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( + let opt = integration_test_utils::make_options_from_args_and_git_config_honoring_env_var( &[], Some(git_config_contents), Some(git_config_path), @@ -196,10 +199,9 @@ pub mod tests { assert_eq!(opt.plus_style, "green"); remove_file(git_config_path).unwrap(); - } - #[test] - fn test_bool_env_var_overrides_git_config() { + // ---------------------------------------------------------------------------------------- + // bool let git_config_contents = b" [delta] side-by-side = true @@ -214,7 +216,7 @@ pub mod tests { 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( + let opt = integration_test_utils::make_options_from_args_and_git_config_honoring_env_var( &[], Some(git_config_contents), Some(git_config_path), @@ -222,10 +224,9 @@ pub mod tests { assert_eq!(opt.side_by_side, false); remove_file(git_config_path).unwrap(); - } - #[test] - fn test_int_env_var_overrides_git_config() { + // ---------------------------------------------------------------------------------------- + // int let git_config_contents = b" [delta] max-line-length = 1 @@ -240,7 +241,7 @@ pub mod tests { 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( + let opt = integration_test_utils::make_options_from_args_and_git_config_honoring_env_var( &[], Some(git_config_contents), Some(git_config_path), @@ -248,17 +249,16 @@ pub mod tests { assert_eq!(opt.max_line_length, 2); remove_file(git_config_path).unwrap(); - } - #[test] - fn test_float_env_var_overrides_git_config() { + // ---------------------------------------------------------------------------------------- + // float 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( + let opt = integration_test_utils::make_options_from_args_and_git_config_honoring_env_var( &[], Some(git_config_contents), Some(git_config_path), @@ -266,7 +266,7 @@ pub mod tests { 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( + let opt = integration_test_utils::make_options_from_args_and_git_config_honoring_env_var( &[], Some(git_config_contents), Some(git_config_path), diff --git a/src/tests/integration_test_utils.rs b/src/tests/integration_test_utils.rs index 37ae0575..8eb36748 100644 --- a/src/tests/integration_test_utils.rs +++ b/src/tests/integration_test_utils.rs @@ -17,12 +17,29 @@ pub mod integration_test_utils { args: &[&str], git_config_contents: Option<&[u8]>, git_config_path: Option<&str>, + ) -> cli::Opt { + _make_options_from_args_and_git_config(args, git_config_contents, git_config_path, false) + } + + pub fn make_options_from_args_and_git_config_honoring_env_var( + args: &[&str], + git_config_contents: Option<&[u8]>, + git_config_path: Option<&str>, + ) -> cli::Opt { + _make_options_from_args_and_git_config(args, git_config_contents, git_config_path, true) + } + + fn _make_options_from_args_and_git_config( + args: &[&str], + git_config_contents: Option<&[u8]>, + git_config_path: Option<&str>, + honor_env_var: bool, ) -> cli::Opt { let mut args: Vec<&str> = itertools::chain(&["/dev/null", "/dev/null"], args) .map(|s| *s) .collect(); let mut git_config = match (git_config_contents, git_config_path) { - (Some(contents), Some(path)) => Some(make_git_config(contents, path)), + (Some(contents), Some(path)) => Some(make_git_config(contents, path, honor_env_var)), _ => { args.push("--no-gitconfig"); None @@ -52,11 +69,11 @@ pub mod integration_test_utils { config::Config::from(make_options_from_args(args)) } - fn make_git_config(contents: &[u8], path: &str) -> GitConfig { + fn make_git_config(contents: &[u8], path: &str, honor_env_var: bool) -> GitConfig { let path = Path::new(path); let mut file = File::create(path).unwrap(); file.write_all(contents).unwrap(); - GitConfig::from_path(&path) + GitConfig::from_path(&path, honor_env_var) } pub fn get_line_of_code_from_delta( -- cgit v1.2.3 From 18719057a9901be9de560cd3ee78b39bd60d9d4b Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Fri, 8 Jan 2021 10:56:23 -0500 Subject: Reorganize git_config module --- src/git_config/git_config.rs | 194 ----------------------------------------- src/git_config/mod.rs | 202 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 197 insertions(+), 199 deletions(-) delete mode 100644 src/git_config/git_config.rs diff --git a/src/git_config/git_config.rs b/src/git_config/git_config.rs deleted file mode 100644 index d95a6b1a..00000000 --- a/src/git_config/git_config.rs +++ /dev/null @@ -1,194 +0,0 @@ -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, -} - -impl GitConfig { - pub fn try_create() -> Option { - let repo = match std::env::current_dir() { - Ok(dir) => git2::Repository::discover(dir).ok(), - _ => None, - }; - let config = match &repo { - Some(repo) => repo.config().ok(), - None => git2::Config::open_default().ok(), - }; - match config { - Some(mut config) => { - let config = config.snapshot().unwrap_or_else(|err| { - eprintln!("Failed to read git config: {}", err); - process::exit(1) - }); - Some(Self { - config, - config_from_env_var: parse_config_from_env_var(), - repo, - enabled: true, - }) - } - None => None, - } - } - - #[cfg(test)] - pub fn from_path(path: &Path, honor_env_var: bool) -> Self { - Self { - config: git2::Config::open(path).unwrap(), - config_from_env_var: if honor_env_var { - parse_config_from_env_var() - } else { - HashMap::new() - }, - repo: None, - enabled: true, - } - } - - pub fn get(&self, key: &str) -> Option - where - T: GitConfigGet, - { - if self.enabled { - T::git_config_get(key, self) - } else { - None - } - } -} - -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 - Self: Sized; -} - -impl GitConfigGet for String { - fn git_config_get(key: &str, git_config: &GitConfig) -> Option { - 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_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 { - 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, - } - } -} - -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/git_config/mod.rs b/src/git_config/mod.rs index 9c7b2212..827446e1 100644 --- a/src/git_config/mod.rs +++ b/src/git_config/mod.rs @@ -1,6 +1,198 @@ -pub mod git_config; -pub mod git_config_entry; +mod git_config_entry; -pub use crate::git_config::git_config::{GitConfig, GitConfigGet}; -pub use crate::git_config::git_config_entry::GitConfigEntry; -pub use crate::git_config::git_config_entry::GitRemoteRepo; +pub use git_config_entry::{GitConfigEntry, GitRemoteRepo}; + +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, +} + +impl GitConfig { + pub fn try_create() -> Option { + let repo = match std::env::current_dir() { + Ok(dir) => git2::Repository::discover(dir).ok(), + _ => None, + }; + let config = match &repo { + Some(repo) => repo.config().ok(), + None => git2::Config::open_default().ok(), + }; + match config { + Some(mut config) => { + let config = config.snapshot().unwrap_or_else(|err| { + eprintln!("Failed to read git config: {}", err); + process::exit(1) + }); + Some(Self { + config, + config_from_env_var: parse_config_from_env_var(), + repo, + enabled: true, + }) + } + None => None, + } + } + + #[cfg(test)] + pub fn from_path(path: &Path, honor_env_var: bool) -> Self { + Self { + config: git2::Config::open(path).unwrap(), + config_from_env_var: if honor_env_var { + parse_config_from_env_var() + } else { + HashMap::new() + }, + repo: None, + enabled: true, + } + } + + pub fn get(&self, key: &str) -> Option + where + T: GitConfigGet, + { + if self.enabled { + T::git_config_get(key, self) + } else { + None + } + } +} + +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 + Self: Sized; +} + +impl GitConfigGet for String { + fn git_config_get(key: &str, git_config: &GitConfig) -> Option { + 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_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 { + 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, + } + } +} + +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""## + ); + } +} -- cgit v1.2.3