Skip to content

Commit

Permalink
Parse GIT_CONFIG_PARAMETERS
Browse files Browse the repository at this point in the history
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
libgit2/libgit2#3854.

Fixes #493
Fixes #307
Ref dandavison/magit-delta#13
  • Loading branch information
dandavison committed Jan 8, 2021
1 parent 42566db commit 3dad510
Show file tree
Hide file tree
Showing 2 changed files with 262 additions and 5 deletions.
100 changes: 95 additions & 5 deletions src/git_config/git_config.rs
Original file line number Diff line number Diff line change
@@ -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<String, String>,
pub enabled: bool,
pub repo: Option<git2::Repository>,
}
Expand All @@ -26,6 +32,7 @@ impl GitConfig {
});
Some(Self {
config,
config_from_env_var: parse_config_from_env_var(),
repo,
enabled: true,
})
Expand All @@ -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,
}
Expand All @@ -55,6 +63,27 @@ impl GitConfig {
}
}

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> {
let mut config = HashMap::new();
for captures in GIT_CONFIG_PARAMETERS_REGEX.captures_iter(s) {
config.insert(captures[1].to_string(), captures[2].to_string());
}
config
}

pub trait GitConfigGet {
fn git_config_get(key: &str, git_config: &GitConfig) -> Option<Self>
where
Expand All @@ -63,27 +92,42 @@ pub trait GitConfigGet {

impl GitConfigGet for String {
fn git_config_get(key: &str, git_config: &GitConfig) -> Option<Self> {
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<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,
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> {
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<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,
Expand All @@ -93,9 +137,55 @@ impl GitConfigGet for usize {

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""##
);
}
}
167 changes: 167 additions & 0 deletions src/options/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

0 comments on commit 3dad510

Please sign in to comment.