summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Davison <dandavison7@gmail.com>2021-01-08 11:36:11 -0500
committerGitHub <noreply@github.com>2021-01-08 11:36:11 -0500
commit3c245e6b55c06cce533606360acd5e0946a4f641 (patch)
tree2c325ca9eb59a9da4782750df56b72fe9b768db7
parent60aa0cc14714da2bdc3b05686253f757869f697c (diff)
parent18719057a9901be9de560cd3ee78b39bd60d9d4b (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.rs101
-rw-r--r--src/git_config/mod.rs202
-rw-r--r--src/options/get.rs167
-rw-r--r--src/tests/integration_test_utils.rs23
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(