diff options
author | Dan Davison <dandavison7@gmail.com> | 2021-01-08 11:36:11 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-08 11:36:11 -0500 |
commit | 3c245e6b55c06cce533606360acd5e0946a4f641 (patch) | |
tree | 2c325ca9eb59a9da4782750df56b72fe9b768db7 | |
parent | 60aa0cc14714da2bdc3b05686253f757869f697c (diff) | |
parent | 18719057a9901be9de560cd3ee78b39bd60d9d4b (diff) |
Merge pull request #495 from dandavison/493-GIT_CONFIG_PARAMETERS
Honor GIT_CONFIG_PARAMETERS env var
-rw-r--r-- | src/git_config/git_config.rs | 101 | ||||
-rw-r--r-- | src/git_config/mod.rs | 202 | ||||
-rw-r--r-- | src/options/get.rs | 167 | ||||
-rw-r--r-- | src/tests/integration_test_utils.rs | 23 |
4 files changed, 384 insertions, 109 deletions
diff --git a/src/git_config/git_config.rs b/src/git_config/git_config.rs deleted file mode 100644 index ab831b31..00000000 --- a/src/git_config/git_config.rs +++ /dev/null @@ -1,101 +0,0 @@ -#[cfg(test)] -use std::path::Path; -use std::process; - -pub struct GitConfig { - config: git2::Config, - pub enabled: bool, - pub repo: Option<git2::Repository>, -} - -impl GitConfig { - pub fn try_create() -> Option<Self> { - 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, - repo, - enabled: true, - }) - } - None => None, - } - } - - #[cfg(test)] - pub fn from_path(path: &Path) -> Self { - Self { - config: git2::Config::open(path).unwrap(), - repo: None, - enabled: true, - } - } - - pub fn get<T>(&self, key: &str) -> Option<T> - where - T: GitConfigGet, - { - if self.enabled { - T::git_config_get(key, self) - } else { - None - } - } -} - -pub trait GitConfigGet { - fn git_config_get(key: &str, git_config: &GitConfig) -> Option<Self> - where - Self: Sized; -} - -impl GitConfigGet for String { - fn git_config_get(key: &str, git_config: &GitConfig) -> Option<Self> { - git_config.config.get_string(key).ok() - } -} - -impl GitConfigGet for Option<String> { - fn git_config_get(key: &str, git_config: &GitConfig) -> Option<Self> { - match git_config.config.get_string(key) { - Ok(value) => Some(Some(value)), - _ => None, - } - } -} - -impl GitConfigGet for bool { - fn git_config_get(key: &str, git_config: &GitConfig) -> Option<Self> { - git_config.config.get_bool(key).ok() - } -} - -impl GitConfigGet for usize { - fn git_config_get(key: &str, git_config: &GitConfig) -> Option<Self> { - 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<Self> { - match git_config.config.get_string(key) { - Ok(value) => value.parse::<f64>().ok(), - _ => None, - } - } -} 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<String, String>, + pub enabled: bool, + pub repo: Option<git2::Repository>, +} + +impl GitConfig { + pub fn try_create() -> Option<Self> { + 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<T>(&self, key: &str) -> Option<T> + where + T: GitConfigGet, + { + if self.enabled { + T::git_config_get(key, self) + } else { + None + } + } +} + +fn parse_config_from_env_var() -> HashMap<String, String> { + 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<String, String> { + 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<Self> + where + Self: Sized; +} + +impl GitConfigGet for String { + fn git_config_get(key: &str, git_config: &GitConfig) -> Option<Self> { + 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<String> { + fn git_config_get(key: &str, git_config: &GitConfig) -> Option<Self> { + 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<Self> { + 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<Self> { + if let Some(s) = git_config.config_from_env_var.get(key) { + if let Ok(n) = s.parse::<usize>() { + 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<Self> { + if let Some(s) = git_config.config_from_env_var.get(key) { + if let Ok(n) = s.parse::<f64>() { + return Some(n); + } + } + match git_config.config.get_string(key) { + Ok(value) => value.parse::<f64>().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..a36987ca 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; + + // 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_env_var_overrides_git_config() { + // ---------------------------------------------------------------------------------------- + // simple string + 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_honoring_env_var( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.plus_style, "green"); + + remove_file(git_config_path).unwrap(); + + // ---------------------------------------------------------------------------------------- + // complex string + 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_honoring_env_var( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.minus_style, r##"magenta italic ol "#aabbcc""##,); + + remove_file(git_config_path).unwrap(); + + // ---------------------------------------------------------------------------------------- + // option string + 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_honoring_env_var( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.plus_style, "green"); + + remove_file(git_config_path).unwrap(); + + // ---------------------------------------------------------------------------------------- + // bool + 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_honoring_env_var( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.side_by_side, false); + + remove_file(git_config_path).unwrap(); + + // ---------------------------------------------------------------------------------------- + // int + 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_honoring_env_var( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.max_line_length, 2); + + remove_file(git_config_path).unwrap(); + + // ---------------------------------------------------------------------------------------- + // 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_honoring_env_var( + &[], + 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_honoring_env_var( + &[], + Some(git_config_contents), + Some(git_config_path), + ); + assert_eq!(opt.max_line_distance, 0.7); + + remove_file(git_config_path).unwrap(); + } +} 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 @@ -18,11 +18,28 @@ pub mod integration_test_utils { 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( |