Repository: dandavison/delta
Pull request #1025 (2022-03-28 23:51:42)

Add custom env struct to store env at init
This allow to no longer ignore some tests (marked previously as FIXME)
by either storing the env at the start of the program or creating a custom env
for test purpose.
This centralize almost all calls to `std::env` inside one wrapper

---

Add a test profile to increase speed for testing (5min -> 20sec on my
machine)

---

clean a few nit in code style like this:
```
if Some(value) = ...
    if value
```
to
```
if Some(true) = ...
```

wescande: You may consider this as a big edit without a lot of added value. And you will not be wrong! :joy: 
I don't know why I was digging into this... But here we are.
dandavison: > You may consider this as a big edit without a lot of added value. And you will not be wrong! 😂 I don't know why I was digging into this... But here we are.

😂 haha!

Wow, the test speed up is incredible, e.g. the slowest test  `test_color_only_output_is_in_one_to_one_correspondence_with_input` 

```
running 1 test
test tests::test_example_diffs::tests::test_color_only_output_is_in_one_to_one_correspondence_with_input ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 344 filtered out; finished in 50.55s
```

```
running 1 test
test tests::test_example_diffs::tests::test_color_only_output_is_in_one_to_one_correspondence_with_input ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 344 filtered out; finished in 3.52s
```

And the test suite just finished while I was writing this comment! Normally there are 2 or 3 linux tests on a different architecture that straggle on for ages...


<img width="1108" alt="image" src="https://user-images.githubusercontent.com/52205/160510604-148d2bb2-80af-431c-b910-c18fdb6b88d0.png">

Are there any reasons we shouldn't be testing with that optimization level? I hope not :) I can't think of any. I guess Rust defaults to the unoptimized to give people faster edit-compile-test cycles when the compilation is a larger proportion of the time.
wescande: > Are there any reasons we shouldn't be testing with that optimization level? 

[The book](https://doc.rust-lang.org/book/ch14-01-release-profiles.html) doesn't say much more than "this settings exist".
As for [the embedded book](https://docs.rust-embedded.org/book/unsorted/speed-vs-size.html#optimize-for-speed), they only talking about speed and LLVM loop optimization.
I found [this issue](https://github.com/rust-lang/cargo/issues/7795) that is good to know.
[This answer](https://stackoverflow.com/a/29822963) is also good to know, but we are already doing what it suggest.

I don't see any reason !
wescande: Also the test you mention is slow mainly because of [`assets.get_syntax_set()`](https://github.com/dandavison/delta/blob/master/src/options/theme.rs#L33) take more than 1 second without the opt.level. (It still the slow point with the opt level, but around 60ms only)
If you multiply that by the [number of entry](https://github.com/dandavison/delta/blob/master/src/tests/test_example_diffs.rs#L835-L922) for this test, you get a very slow test compare to other.

But I don't know how to replace this call. :sweat_smile: 

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 5aff965..6d5f751 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -63,3 +63,6 @@ features = []
 version = "0.12.4"
 default-features = false
 features = []
+
+[profile.test]
+opt-level = 2
diff --git a/src/cli.rs b/src/cli.rs
index cfc72a7..7bb4140 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -9,6 +9,7 @@ use syntect::highlighting::Theme as SyntaxTheme;
 use syntect::parsing::SyntaxSet;
 
 use crate::config::delta_unreachable;
+use crate::env::DeltaEnv;
 use crate::git_config::{GitConfig, GitConfigEntry};
 use crate::options;
 use crate::utils;
@@ -1072,6 +1073,9 @@ pub struct Opt {
 
     #[clap(skip)]
     pub git_config_entries: HashMap<String, GitConfigEntry>,
+
+    #[clap(skip)]
+    pub env: DeltaEnv,
 }
 
 #[derive(Default, Clone, Debug)]
@@ -1120,28 +1124,40 @@ impl Default for PagingMode {
 
 impl Opt {
     pub fn from_args_and_git_config(
+        env: DeltaEnv,
         git_config: Option<GitConfig>,
         assets: HighlightingAssets,
     ) -> Self {
-        Self::from_clap_and_git_config(Self::into_app().get_matches(), git_config, assets)
+        Self::from_clap_and_git_config(env, Self::into_app().get_matches(), git_config, assets)
     }
 
-    pub fn from_iter_and_git_config<I>(iter: I, git_config: Option<GitConfig>) -> Self
+    pub fn from_iter_and_git_config<I>(
+        env: DeltaEnv,
+        iter: I,
+        git_config: Option<GitConfig>,
+    ) -> Self
     where
         I: IntoIterator,
         I::Item: Into<OsString> + Clone,
     {
         let assets = utils::bat::assets::load_highlighting_assets();
-        Self::from_clap_and_git_config(Self::into_app().get_matches_from(iter), git_config, assets)
+        Self::from_clap_and_git_config(
+            env,
+            Self::into_app().get_matches_from(iter),
+            git_config,
+            assets,
+        )
     }
 
     fn from_clap_and_git_config(
+        env: DeltaEnv,
         arg_matches: clap::ArgMatches,
         mut git_config: Option<GitConfig>,
         assets: HighlightingAssets,
     ) -> Self {
         let mut opt = Opt::from_arg_matches(&arg_matches)
             .unwrap_or_else(|_| delta_unreachable("Opt::from_arg_matches failed"));
+        opt.env = env;
         options::set::set_options(&mut opt, &mut git_config, &arg_matches, assets);
         opt.git_config = git_config;
         opt
diff --git a/src/config.rs b/src/config.rs
index 24fe681..c6d7ef5 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -10,7 +10,6 @@ use crate::ansi;
 use crate::cli;
 use crate::color;
 use crate::delta::State;
-use crate::env;
 use crate::fatal;
 use crate::features::navigate;
 use crate::features::side_by_side::{self, ansifill, LeftRight};
@@ -151,10 +150,12 @@ impl From<cli::Opt> for Config {
 
         let wrap_config = WrapConfig::from_opt(&opt, styles["inline-hint-style"]);
 
-        let max_line_distance_for_naively_paired_lines =
-            env::get_env_var("DELTA_EXPERIMENTAL_MAX_LINE_DISTANCE_FOR_NAIVELY_PAIRED_LINES")
-                .map(|s| s.parse::<f64>().unwrap_or(0.0))
-                .unwrap_or(0.0);
+        let max_line_distance_for_naively_paired_lines = opt
+            .env
+            .experimental_max_line_distance_for_naively_paired_lines
+            .as_ref()
+            .map(|s| s.parse::<f64>().unwrap_or(0.0))
+            .unwrap_or(0.0);
 
         let commit_regex = Regex::new(&opt.commit_regex).unwrap_or_else(|_| {
             fatal(format!(
@@ -217,11 +218,11 @@ impl From<cli::Opt> for Config {
         };
 
         #[cfg(not(test))]
-        let cwd_of_delta_process = std::env::current_dir().ok();
+        let cwd_of_delta_process = opt.env.current_dir;
         #[cfg(test)]
         let cwd_of_delta_process = Some(utils::path::fake_delta_cwd_for_tests());
 
-        let cwd_relative_to_repo_root = std::env::var("GIT_PREFIX").ok();
+        let cwd_relative_to_repo_root = opt.env.git_prefix;
 
         let cwd_of_user_shell_process = utils::path::cwd_of_user_shell_process(
             cwd_of_delta_process.as_ref(),
diff --git a/src/delta.rs b/src/delta.rs
index 7ba2997..27b3051 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -157,6 +157,7 @@ impl<'a> StateMachine<'a> {
             let _ = self.handle_commit_meta_header_line()?
                 || self.handle_diff_stat_line()?
                 || self.handle_diff_header_diff_line()?
+                || self.handle_diff_header_file_operation_line()?
                 || self.handle_diff_header_minus_line()?
                 || self.handle_diff_header_plus_line()?
                 || self.handle_hunk_header_line()?
@@ -173,7 +174,7 @@ impl<'a> StateMachine<'a> {
                 || self.emit_line_unchanged()?;
         }
 
-        self.handle_pending_mode_line_with_diff_name()?;
+        self.handle_pending_line_with_diff_name()?;
         self.painter.paint_buffered_minus_and_plus_lines();
         self.painter.emit()?;
         Ok(())
diff --git a/src/env.rs b/src/env.rs
index 5eb06cc..8735d91 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -1,24 +1,73 @@
-#[cfg(not(test))]
 use std::env;
 
-/// If `name` is set and, after trimming whitespace, is not empty string, then return that trimmed
-/// string. Else None.
-pub fn get_env_var(_name: &str) -> Option<String> {
-    #[cfg(not(test))]
-    match env::var(_name).unwrap_or_else(|_| "".to_string()).trim() {
-        "" => None,
-        non_empty_string => Some(non_empty_string.to_string()),
+const COLORTERM: &str = "COLORTERM";
+const BAT_THEME: &str = "BAT_THEME";
+const GIT_CONFIG_PARAMETERS: &str = "GIT_CONFIG_PARAMETERS";
+const GIT_PREFIX: &str = "GIT_PREFIX";
+const DELTA_FEATURES: &str = "DELTA_FEATURES";
+const DELTA_NAVIGATE: &str = "DELTA_NAVIGATE";
+const DELTA_EXPERIMENTAL_MAX_LINE_DISTANCE_FOR_NAIVELY_PAIRED_LINES: &str =
+    "DELTA_EXPERIMENTAL_MAX_LINE_DISTANCE_FOR_NAIVELY_PAIRED_LINES";
+const DELTA_PAGER: &str = "DELTA_PAGER";
+const BAT_PAGER: &str = "BAT_PAGER";
+const PAGER: &str = "PAGER";
+
+#[derive(Default, Clone)]
+pub struct DeltaEnv {
+    pub bat_theme: Option<String>,
+    pub colorterm: Option<String>,
+    pub current_dir: Option<std::path::PathBuf>,
+    pub experimental_max_line_distance_for_naively_paired_lines: Option<String>,
+    pub features: Option<String>,
+    pub git_config_parameters: Option<String>,
+    pub git_prefix: Option<String>,
+    pub navigate: Option<String>,
+    pub pagers: (Option<String>, Option<String>, Option<String>),
+}
+
+impl DeltaEnv {
+    /// Create a structure with current environment variable
+    pub fn init() -> Self {
+        let bat_theme = env::var(BAT_THEME).ok();
+        let colorterm = env::var(COLORTERM).ok();
+        let experimental_max_line_distance_for_naively_paired_lines =
+            env::var(DELTA_EXPERIMENTAL_MAX_LINE_DISTANCE_FOR_NAIVELY_PAIRED_LINES).ok();
+        let features = env::var(DELTA_FEATURES).ok();
+        let git_config_parameters = env::var(GIT_CONFIG_PARAMETERS).ok();
+        let git_prefix = env::var(GIT_PREFIX).ok();
+        let navigate = env::var(DELTA_NAVIGATE).ok();
+
+        let current_dir = env::current_dir().ok();
+        let pagers = (
+            env::var(DELTA_PAGER).ok(),
+            env::var(BAT_PAGER).ok(),
+            env::var(PAGER).ok(),
+        );
+
+        Self {
+            bat_theme,
+            colorterm,
+            current_dir,
+            experimental_max_line_distance_for_naively_paired_lines,
+            features,
+            git_config_parameters,
+            git_prefix,
+            navigate,
+            pagers,
+        }
     }
-    #[cfg(test)]
-    None
 }
 
-/// If `name` is set to any value at all (including "") then return true; else false.
-pub fn get_boolean_env_var(_name: &str) -> bool {
-    #[cfg(not(test))]
-    {
-        env::var(_name).ok().is_some()
+#[cfg(test)]
+pub mod tests {
+    use super::DeltaEnv;
+    use std::env;
+
+    #[test]
+    fn test_env_parsing() {
+        let feature = "Awesome Feature";
+        env::set_var("DELTA_FEATURES", feature);
+        let env = DeltaEnv::init();
+        assert_eq!(env.features, Some(feature.into()));
     }
-    #[cfg(test)]
-    false
 }
diff --git a/src/features/mod.rs b/src/features/mod.rs
index 5be2b3d..8234d62 100644
--- a/src/features/mod.rs
+++ b/src/features/mod.rs
@@ -94,6 +94,7 @@ pub mod tests {
     use std::fs::remove_file;
 
     use crate::cli;
+    use crate::env::DeltaEnv;
     use crate::features::make_builtin_features;
     use crate::tests::integration_test_utils::make_options_from_args_and_git_config;
 
@@ -102,7 +103,7 @@ pub mod tests {
         let builtin_features = make_builtin_features();
         let mut args = vec!["delta".to_string()];
         args.extend(builtin_features.keys().map(|s| format!("--{}", s)));
-        let opt = cli::Opt::from_iter_and_git_config(args, None);
+        let opt = cli::Opt::from_iter_and_git_config(DeltaEnv::default(), args, None);
         let features: HashSet<&str> = opt
             .features
             .as_deref()
diff --git a/src/git_config/mod.rs b/src/git_config/mod.rs
index 2151725..6b87444 100644
--- a/src/git_config/mod.rs
+++ b/src/git_config/mod.rs
@@ -2,9 +2,9 @@ mod git_config_entry;
 
 pub use git_config_entry::{GitConfigEntry, GitRemoteRepo};
 
+use crate::env::DeltaEnv;
 use regex::Regex;
 use std::collections::HashMap;
-use std::env;
 #[cfg(test)]
 use std::path::Path;
 
@@ -37,11 +37,11 @@ impl Clone for GitConfig {
 
 impl GitConfig {
     #[cfg(not(test))]
-    pub fn try_create() -> Option<Self> {
+    pub fn try_create(env: &DeltaEnv) -> Option<Self> {
         use crate::fatal;
 
-        let repo = match std::env::current_dir() {
-            Ok(dir) => git2::Repository::discover(dir).ok(),
+        let repo = match &env.current_dir {
+            Some(dir) => git2::Repository::discover(dir).ok(),
             _ => None,
         };
         let config = match &repo {
@@ -55,7 +55,7 @@ impl GitConfig {
                 });
                 Some(Self {
                     config,
-                    config_from_env_var: parse_config_from_env_var(),
+                    config_from_env_var: parse_config_from_env_var(env),
                     repo,
                     enabled: true,
                 })
@@ -65,16 +65,16 @@ impl GitConfig {
     }
 
     #[cfg(test)]
-    pub fn try_create() -> Option<Self> {
+    pub fn try_create(_env: &DeltaEnv) -> Option<Self> {
         unreachable!("GitConfig::try_create() is not available when testing");
     }
 
     #[cfg(test)]
-    pub fn from_path(path: &Path, honor_env_var: bool) -> Self {
+    pub fn from_path(env: &DeltaEnv, 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()
+                parse_config_from_env_var(env)
             } else {
                 HashMap::new()
             },
@@ -96,9 +96,9 @@ 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)
+fn parse_config_from_env_var(env: &DeltaEnv) -> HashMap<String, String> {
+    if let Some(s) = &env.git_config_parameters {
+        parse_config_from_env_var_value(s)
     } else {
         HashMap::new()
     }
diff --git a/src/handlers/diff_header.rs b/src/handlers/diff_header.rs
index 3e317be..39262c1 100644
--- a/src/handlers/diff_header.rs
+++ b/src/handlers/diff_header.rs
@@ -14,9 +14,11 @@ const DIFF_PREFIXES: [&str; 6] = ["a/", "b/", "c/", "i/", "o/", "w/"];
 
 #[derive(Debug, PartialEq)]
 pub enum FileEvent {
+    Added,
     Change,
     Copy,
     Rename,
+    Removed,
     NoEvent,
 }
 
@@ -49,6 +51,25 @@ impl<'a> StateMachine<'a> {
         Ok(handled_line)
     }
 
+    fn should_write_generic_diff_header_header_line(&mut self) -> std::io::Result<bool> {
+        // In color_only mode, raw_line's structure shouldn't be changed.
+        // So it needs to avoid fn _handle_diff_header_header_line
+        // (it connects the plus_file and minus_file),
+        // and to call fn handle_generic_diff_header_header_line directly.
+        if self.config.color_only {
+            write_generic_diff_header_header_line(
+                &self.line,
+                &self.raw_line,
+                &mut self.painter,
+                &mut self.mode_info,
+                self.config,
+            )?;
+            Ok(true)
+        } else {
+            Ok(false)
+        }
+    }
+
     #[inline]
     fn test_diff_header_minus_line(&self) -> bool {
         (matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified)
@@ -62,7 +83,6 @@ impl<'a> StateMachine<'a> {
         if !self.test_diff_header_minus_line() {
             return Ok(false);
         }
-        let mut handled_line = false;
 
         let (path_or_mode, file_event) =
             parse_diff_header_line(&self.line, self.source == Source::GitDiff);
@@ -84,21 +104,7 @@ impl<'a> StateMachine<'a> {
         }
 
         self.painter.paint_buffered_minus_and_plus_lines();
-        // In color_only mode, raw_line's structure shouldn't be changed.
-        // So it needs to avoid fn _handle_diff_header_header_line
-        // (it connects the plus_file and minus_file),
-        // and to call fn handle_generic_diff_header_header_line directly.
-        if self.config.color_only {
-            write_generic_diff_header_header_line(
-                &self.line,
-                &self.raw_line,
-                &mut self.painter,
-                &mut self.mode_info,
-                self.config,
-            )?;
-            handled_line = true;
-        }
-        Ok(handled_line)
+        self.should_write_generic_diff_header_header_line()
     }
 
     #[inline]
@@ -129,25 +135,58 @@ impl<'a> StateMachine<'a> {
         self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));
 
         self.painter.paint_buffered_minus_and_plus_lines();
-        // In color_only mode, raw_line's structure shouldn't be changed.
-        // So it needs to avoid fn _handle_diff_header_header_line
-        // (it connects the plus_file and minus_file),
-        // and to call fn handle_generic_diff_header_header_line directly.
-        if self.config.color_only {
-            write_generic_diff_header_header_line(
-                &self.line,
-                &self.raw_line,
-                &mut self.painter,
-                &mut self.mode_info,
-                self.config,
-            )?;
-            handled_line = true
+        if self.should_write_generic_diff_header_header_line()? {
+            handled_line = true;
         } else if self.should_handle()
             && self.handled_diff_header_header_line_file_pair != self.current_file_pair
         {
             self.painter.emit()?;
             self._handle_diff_header_header_line(self.source == Source::DiffUnified)?;
-            self.handled_diff_header_header_line_file_pair = self.current_file_pair.clone()
+            self.handled_diff_header_header_line_file_pair = self.current_file_pair.clone();
+        }
+        Ok(handled_line)
+    }
+
+    #[inline]
+    fn test_diff_header_file_operation_line(&self) -> bool {
+        (matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified)
+            && (self.line.starts_with("deleted file mode ")
+                || self.line.starts_with("new file mode "))
+    }
+
+    /// Check for and handle the "deleted file ..."  line.
+    pub fn handle_diff_header_file_operation_line(&mut self) -> std::io::Result<bool> {
+        if !self.test_diff_header_file_operation_line() {
+            return Ok(false);
+        }
+        let mut handled_line = false;
+        let (_mode_info, file_event) =
+            parse_diff_header_line(&self.line, self.source == Source::GitDiff);
+        let name = get_repeated_file_path_from_diff_line(&self.diff_line)
+            .unwrap_or_else(|| "".to_string());
+        match file_event {
+            FileEvent::Removed => {
+                self.minus_file = name;
+                self.plus_file = "/dev/null".into();
+                self.minus_file_event = FileEvent::Change;
+                self.plus_file_event = FileEvent::Change;
+                self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));
+            }
+            FileEvent::Added => {
+                self.minus_file = "/dev/null".into();
+                self.plus_file = name;
+                self.minus_file_event = FileEvent::Change;
+                self.plus_file_event = FileEvent::Change;
+                self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));
+            }
+            _ => (),
+        }
+
+        if self.should_write_generic_diff_header_header_line()?
+            || (self.should_handle()
+                && self.handled_diff_header_header_line_file_pair != self.current_file_pair)
+        {
+            handled_line = true;
         }
         Ok(handled_line)
     }
@@ -172,7 +211,16 @@ impl<'a> StateMachine<'a> {
         )
     }
 
-    pub fn handle_pending_mode_line_with_diff_name(&mut self) -> std::io::Result<()> {
+    #[inline]
+    fn test_pending_line_with_diff_name(&self) -> bool {
+        matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified
+    }
+
+    pub fn handle_pending_line_with_diff_name(&mut self) -> std::io::Result<()> {
+        if !self.test_pending_line_with_diff_name() {
+            return Ok(());
+        }
+
         if !self.mode_info.is_empty() {
             let format_label = |label: &str| {
                 if !label.is_empty() {
@@ -204,6 +252,13 @@ impl<'a> StateMachine<'a> {
                 &mut self.mode_info,
                 self.config,
             )
+        } else if !self.config.color_only
+            && self.should_handle()
+            && self.handled_diff_header_header_line_file_pair != self.current_file_pair
+        {
+            self._handle_diff_header_header_line(self.source == Source::DiffUnified)?;
+            self.handled_diff_header_header_line_file_pair = self.current_file_pair.clone();
+            Ok(())
         } else {
             Ok(())
         }
@@ -292,6 +347,12 @@ fn parse_diff_header_line(line: &str, git_diff_name: bool) -> (String, FileEvent
         line if line.starts_with("copy to ") => {
             (line[8..].to_string(), FileEvent::Copy) // "copy to ".len()
         }
+        line if line.starts_with("new file mode ") => {
+            (line[14..].to_string(), FileEvent::Added) // "new file mode ".len()
+        }
+        line if line.starts_with("deleted file mode ") => {
+            (line[18..].to_string(), FileEvent::Removed) // "deleted file mode ".len()
+        }
         _ => ("".to_string(), FileEvent::NoEvent),
     }
 }
diff --git a/src/handlers/diff_header_diff.rs b/src/handlers/diff_header_diff.rs
index fdd2d39..a79f6e1 100644
--- a/src/handlers/diff_header_diff.rs
+++ b/src/handlers/diff_header_diff.rs
@@ -22,7 +22,7 @@ impl<'a> StateMachine<'a> {
             } else {
                 State::DiffHeader(DiffType::Unified)
             };
-        self.handle_pending_mode_line_with_diff_name()?;
+        self.handle_pending_line_with_diff_name()?;
         self.handled_diff_header_header_line_file_pair = None;
         self.diff_line = self.line.clone();
         if !self.should_skip_line() {
diff --git a/src/main.rs b/src/main.rs
index cfa076e..1d2b763 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -86,7 +86,12 @@ fn main() -> std::io::Result<()> {
 // arguments and without standard input; 2 is used to report a real problem.
 fn run_app() -> std::io::Result<i32> {
     let assets = utils::bat::assets::load_highlighting_assets();
-    let opt = cli::Opt::from_args_and_git_config(git_config::GitConfig::try_create(), assets);
+    let env = env::DeltaEnv::init();
+    let opt = cli::Opt::from_args_and_git_config(
+        env.clone(),
+        git_config::GitConfig::try_create(&env),
+        assets,
+    );
 
     let subcommand_result = if opt.list_languages {
         Some(list_languages())
@@ -128,7 +133,7 @@ fn run_app() -> std::io::Result<i32> {
     }
 
     let mut output_type =
-        OutputType::from_mode(config.paging_mode, config.pager.clone(), &config).unwrap();
+        OutputType::from_mode(&env, config.paging_mode, config.pager.clone(), &config).unwrap();
     let mut writer = output_type.handle().unwrap();
 
     match (config.minus_file.as_ref(), config.plus_file.as_ref()) {
diff --git a/src/options/get.rs b/src/options/get.rs
index 0dd02b9..a09d166 100644
--- a/src/options/get.rs
+++ b/src/options/get.rs
@@ -137,173 +137,138 @@ impl GetOptionValue for usize {}
 
 #[cfg(test)]
 pub mod tests {
-    use std::env;
     use std::fs::remove_file;
 
+    use crate::cli::Opt;
+    use crate::env::DeltaEnv;
     use crate::options::get::get_themes;
     use crate::tests::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";
-
+    // fn generic<T>(_s: SGen<T>) {}
+    fn _test_env_var_overrides_git_config_generic(
+        git_config_contents: &[u8],
+        git_config_path: &str,
+        env_value: String,
+        fn_cmp_before: &dyn Fn(Opt),
+        fn_cmp_after: &dyn Fn(Opt),
+    ) {
         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");
+        fn_cmp_before(opt);
 
-        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(
+        let opt = integration_test_utils::make_options_from_args_and_git_config_honoring_env_var_with_custom_env(
+            DeltaEnv {
+                git_config_parameters: Some(env_value),
+                ..DeltaEnv::default()
+            },
             &[],
             Some(git_config_contents),
             Some(git_config_path),
         );
-        assert_eq!(opt.plus_style, "green");
+        fn_cmp_after(opt);
 
         remove_file(git_config_path).unwrap();
+    }
+    #[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";
+        _test_env_var_overrides_git_config_generic(
+            git_config_contents,
+            git_config_path,
+            "'delta.plus-style=green'".into(),
+            &|opt: Opt| assert_eq!(opt.plus_style, "blue"),
+            &|opt: Opt| assert_eq!(opt.plus_style, "green"),
+        );
+    }
 
-        // ----------------------------------------------------------------------------------------
-        // complex string
+    #[test]
+    fn test_env_var_overrides_git_config_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),
+        _test_env_var_overrides_git_config_generic(
+            git_config_contents,
+            git_config_path,
+            r##"'delta.minus-style=magenta italic ol "#aabbcc"'"##.into(),
+            &|opt: Opt| assert_eq!(opt.minus_style, r##"red bold ul #ffeeee"##),
+            &|opt: Opt| assert_eq!(opt.minus_style, r##"magenta italic ol "#aabbcc""##,),
         );
-        assert_eq!(opt.minus_style, r##"magenta italic ol "#aabbcc""##,);
-
-        remove_file(git_config_path).unwrap();
+    }
 
-        // ----------------------------------------------------------------------------------------
-        // option string
+    #[test]
+    fn test_env_var_overrides_git_config_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),
+        _test_env_var_overrides_git_config_generic(
+            git_config_contents,
+            git_config_path,
+            "'delta.plus-style=green'".into(),
+            &|opt: Opt| assert_eq!(opt.plus_style, "blue"),
+            &|opt: Opt| assert_eq!(opt.plus_style, "green"),
         );
-        assert_eq!(opt.plus_style, "green");
-
-        remove_file(git_config_path).unwrap();
+    }
 
-        // ----------------------------------------------------------------------------------------
-        // bool
+    #[test]
+    fn test_env_var_overrides_git_config_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),
+        _test_env_var_overrides_git_config_generic(
+            git_config_contents,
+            git_config_path,
+            "'delta.side-by-side=false'".into(),
+            &|opt: Opt| assert_eq!(opt.side_by_side, true),
+            &|opt: Opt| assert_eq!(opt.side_by_side, false),
         );
-        assert_eq!(opt.side_by_side, false);
-
-        remove_file(git_config_path).unwrap();
+    }
 
-        // ----------------------------------------------------------------------------------------
-        // int
+    #[test]
+    fn test_env_var_overrides_git_config_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),
+        _test_env_var_overrides_git_config_generic(
+            git_config_contents,
+            git_config_path,
+            "'delta.max-line-length=2'".into(),
+            &|opt: Opt| assert_eq!(opt.max_line_length, 1),
+            &|opt: Opt| assert_eq!(opt.max_line_length, 2),
         );
-        assert_eq!(opt.max_line_length, 2);
-
-        remove_file(git_config_path).unwrap();
+    }
 
-        // ----------------------------------------------------------------------------------------
-        // float
+    #[test]
+    fn test_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_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),
+        _test_env_var_overrides_git_config_generic(
+            git_config_contents,
+            git_config_path,
+            "'delta.max-line-distance=0.7'".into(),
+            &|opt: Opt| assert_eq!(opt.max_line_distance, 0.6),
+            &|opt: Opt| assert_eq!(opt.max_line_distance, 0.7),
         );
-        assert_eq!(opt.max_line_distance, 0.7);
-
-        remove_file(git_config_path).unwrap();
     }
 
     #[test]
-    #[ignore] // FIXME
     fn test_delta_features_env_var() {
         let git_config_contents = b"
 [delta]
@@ -319,22 +284,32 @@ pub mod tests {
         assert_eq!(opt.features.unwrap(), "feature-from-gitconfig");
         assert_eq!(opt.side_by_side, false);
 
-        env::set_var("DELTA_FEATURES", "side-by-side");
-        let opt = integration_test_utils::make_options_from_args_and_git_config(
+        let opt = integration_test_utils::make_options_from_args_and_git_config_with_custom_env(
+            DeltaEnv {
+                features: Some("side-by-side".into()),
+                ..DeltaEnv::default()
+            },
             &[],
             Some(git_config_contents),
             Some(git_config_path),
         );
-        assert_eq!(opt.features.unwrap(), "side-by-side");
+        // `line-numbers` is a builtin feature induced by side-by-side
+        assert_eq!(opt.features.unwrap(), "line-numbers side-by-side");
         assert_eq!(opt.side_by_side, true);
 
-        env::set_var("DELTA_FEATURES", "+side-by-side");
-        let opt = integration_test_utils::make_options_from_args_and_git_config(
+        let opt = integration_test_utils::make_options_from_args_and_git_config_with_custom_env(
+            DeltaEnv {
+                features: Some("+side-by-side".into()),
+                ..DeltaEnv::default()
+            },
             &[],
             Some(git_config_contents),
             Some(git_config_path),
         );
-        assert_eq!(opt.features.unwrap(), "side-by-side feature-from-gitconfig");
+        assert_eq!(
+            opt.features.unwrap(),
+            "feature-from-gitconfig line-numbers side-by-side"
+        );
         assert_eq!(opt.side_by_side, true);
 
         remove_file(git_config_path).unwrap();
@@ -365,6 +340,7 @@ pub mod tests {
         let git_config_path = "delta__test_get_themes_git_config.gitconfig";
 
         let git_config = Some(integration_test_utils::make_git_config(
+            &DeltaEnv::default(),
             git_config_contents.as_bytes(),
             git_config_path,
             false,
diff --git a/src/options/set.rs b/src/options/set.rs
index 57d95b2..4177651 100644
--- a/src/options/set.rs
+++ b/src/options/set.rs
@@ -8,7 +8,7 @@ use console::Term;
 
 use crate::cli;
 use crate::config;
-use crate::env;
+use crate::env::DeltaEnv;
 use crate::errors::*;
 use crate::fatal;
 use crate::features;
@@ -76,9 +76,9 @@ pub fn set_options(
         }
         set_git_config_entries(opt, git_config);
     }
-    opt.navigate = opt.navigate || env::get_boolean_env_var("DELTA_NAVIGATE");
+    opt.navigate = opt.navigate || opt.env.navigate.is_some();
     if opt.syntax_theme.is_none() {
-        opt.syntax_theme = env::get_env_var("BAT_THEME")
+        opt.syntax_theme = opt.env.bat_theme.clone();
     }
 
     let option_names = cli::Opt::get_argument_and_option_names();
@@ -336,7 +336,7 @@ fn gather_features(
     builtin_features: &HashMap<String, features::BuiltinFeature>,
     git_config: &Option<GitConfig>,
 ) -> Vec<String> {
-    let from_env_var = env::get_env_var("DELTA_FEATURES");
+    let from_env_var = &opt.env.features;
     let from_args = opt.features.as_deref().unwrap_or("");
     let input_features: Vec<&str> = match from_env_var.as_deref() {
         Some(from_env_var) if from_env_var.starts_with('+') => from_env_var[1..]
@@ -463,12 +463,9 @@ fn gather_builtin_features_from_flags_in_gitconfig(
     git_config: &GitConfig,
 ) {
     for child_feature in builtin_features.keys() {
-        if let Some(value) =
-            git_config.get::<bool>(&format!("{}.{}", git_config_key, child_feature))
+        if let Some(true) = git_config.get::<bool>(&format!("{}.{}", git_config_key, child_feature))
         {
-            if value {
-                gather_builtin_features_recursively(child_feature, features, builtin_features, opt);
-            }
+            gather_builtin_features_recursively(child_feature, features, builtin_features, opt);
         }
     }
 }
@@ -507,17 +504,15 @@ fn gather_builtin_features_recursively(
         }
         for child_feature in builtin_features.keys() {
             if let Some(child_features_fn) = feature_data.get(child_feature) {
-                if let ProvenancedOptionValue::DefaultValue(OptionValue::Boolean(value)) =
+                if let ProvenancedOptionValue::DefaultValue(OptionValue::Boolean(true)) =
                     child_features_fn(opt, &None)
                 {
-                    if value {
-                        gather_builtin_features_recursively(
-                            child_feature,
-                            features,
-                            builtin_features,
-                            opt,
-                        );
-                    }
+                    gather_builtin_features_recursively(
+                        child_feature,
+                        features,
+                        builtin_features,
+                        opt,
+                    );
                 }
             }
         }
@@ -645,7 +640,7 @@ fn set_true_color(opt: &mut cli::Opt) {
     opt.computed.true_color = match opt.true_color.as_ref() {
         "always" => true,
         "never" => false,
-        "auto" => is_truecolor_terminal(),
+        "auto" => is_truecolor_terminal(&opt.env),
         _ => {
             fatal(format!(
                 "Invalid value for --true-color option: {} (valid values are \"always\", \"never\", and \"auto\")",
@@ -655,8 +650,9 @@ fn set_true_color(opt: &mut cli::Opt) {
     };
 }
 
-fn is_truecolor_terminal() -> bool {
-    env::get_env_var("COLORTERM")
+fn is_truecolor_terminal(env: &DeltaEnv) -> bool {
+    env.colorterm
+        .as_ref()
         .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
         .unwrap_or(false)
 }
diff --git a/src/options/theme.rs b/src/options/theme.rs
index 642699c..2d853ad 100644
--- a/src/options/theme.rs
+++ b/src/options/theme.rs
@@ -10,14 +10,13 @@ use bat;
 use bat::assets::HighlightingAssets;
 
 use crate::cli;
-use crate::env;
 
 #[allow(non_snake_case)]
 pub fn set__is_light_mode__syntax_theme__syntax_set(
     opt: &mut cli::Opt,
     assets: HighlightingAssets,
 ) {
-    let syntax_theme_name_from_bat_theme = env::get_env_var("BAT_THEME");
+    let syntax_theme_name_from_bat_theme = &opt.env.bat_theme;
     let (is_light_mode, syntax_theme_name) = get_is_light_mode_and_syntax_theme_name(
         opt.syntax_theme.as_ref(),
         syntax_theme_name_from_bat_theme.as_ref(),
diff --git a/src/subcommands/show_colors.rs b/src/subcommands/show_colors.rs
index 09a8f19..7aaa716 100644
--- a/src/subcommands/show_colors.rs
+++ b/src/subcommands/show_colors.rs
@@ -3,6 +3,7 @@ use crate::color;
 use crate::colors;
 use crate::config;
 use crate::delta;
+use crate::env::DeltaEnv;
 use crate::git_config;
 use crate::paint;
 use crate::paint::BgShouldFill;
@@ -16,11 +17,16 @@ pub fn show_colors() -> std::io::Result<()> {
     use crate::{delta::DiffType, utils};
 
     let assets = utils::bat::assets::load_highlighting_assets();
-    let opt = cli::Opt::from_args_and_git_config(git_config::GitConfig::try_create(), assets);
+    let env = DeltaEnv::default();
+    let opt = cli::Opt::from_args_and_git_config(
+        env.clone(),
+        git_config::GitConfig::try_create(&env),
+        assets,
+    );
     let config = config::Config::from(opt);
 
     let mut output_type =
-        OutputType::from_mode(PagingMode::QuitIfOneScreen, None, &config).unwrap();
+        OutputType::from_mode(&env, PagingMode::QuitIfOneScreen, None, &config).unwrap();
     let writer = output_type.handle().unwrap();
 
     let mut painter = paint::Painter::new(writer, &config);
diff --git a/src/subcommands/show_syntax_themes.rs b/src/subcommands/show_syntax_themes.rs
index be7478a..15fe0e2 100644
--- a/src/subcommands/show_syntax_themes.rs
+++ b/src/subcommands/show_syntax_themes.rs
@@ -1,6 +1,7 @@
 use crate::cli;
 use crate::config;
 use crate::delta;
+use crate::env::DeltaEnv;
 use crate::options::theme::is_light_syntax_theme;
 use crate::utils;
 use crate::utils::bat::output::{OutputType, PagingMode};
@@ -9,8 +10,10 @@ use std::io::{self, ErrorKind, Read, Write};
 
 #[cfg(not(tarpaulin_include))]
 pub fn show_syntax_themes() -> std::io::Result<()> {
+    let env = DeltaEnv::default();
     let assets = utils::bat::assets::load_highlighting_assets();
     let mut output_type = OutputType::from_mode(
+        &env,
         PagingMode::QuitIfOneScreen,
         None,
         &config::Config::from(cli::Opt::parse()),
diff --git a/src/subcommands/show_themes.rs b/src/subcommands/show_themes.rs
index 668c6f9..f168c21 100644
--- a/src/subcommands/show_themes.rs
+++ b/src/subcommands/show_themes.rs
@@ -3,6 +3,7 @@ use std::io::{self, ErrorKind, Read};
 use crate::cli;
 use crate::config;
 use crate::delta;
+use crate::env::DeltaEnv;
 use crate::git_config;
 use crate::options::get::get_themes;
 use crate::utils::bat::output::{OutputType, PagingMode};
@@ -24,17 +25,25 @@ pub fn show_themes(dark: bool, light: bool, computed_theme_is_light: bool) -> st
         }
     };
 
-    let git_config = git_config::GitConfig::try_create();
-    let opt =
-        cli::Opt::from_iter_and_git_config(&["", "", "--navigate", "--show-themes"], git_config);
+    let env = DeltaEnv::default();
+    let git_config = git_config::GitConfig::try_create(&env);
+    let opt = cli::Opt::from_iter_and_git_config(
+        env.clone(),
+        &["", "", "--navigate", "--show-themes"],
+        git_config,
+    );
     let mut output_type =
-        OutputType::from_mode(PagingMode::Always, None, &config::Config::from(opt)).unwrap();
+        OutputType::from_mode(&env, PagingMode::Always, None, &config::Config::from(opt)).unwrap();
     let title_style = ansi_term::Style::new().bold();
     let writer = output_type.handle().unwrap();
 
-    for theme in &get_themes(git_config::GitConfig::try_create()) {
-        let git_config = git_config::GitConfig::try_create();
-        let opt = cli::Opt::from_iter_and_git_config(&["", "", "--features", theme], git_config);
+    for theme in &get_themes(git_config::GitConfig::try_create(&env)) {
+        let git_config = git_config::GitConfig::try_create(&env);
+        let opt = cli::Opt::from_iter_and_git_config(
+            env.clone(),
+            &["", "", "--features", theme],
+            git_config,
+        );
         let is_dark_theme = opt.dark;
         let is_light_theme = opt.light;
         let config = config::Config::from(opt);
diff --git a/src/utils/bat/output.rs b/src/utils/bat/output.rs
index 62761c2..22697d4 100644
--- a/src/utils/bat/output.rs
+++ b/src/utils/bat/output.rs
@@ -1,7 +1,6 @@
 // https://github.com/sharkdp/bat a1b9334a44a2c652f52dddaa83dbacba57372468
 // src/output.rs
 // See src/utils/bat/LICENSE
-use std::env;
 use std::ffi::OsString;
 use std::io::{self, Write};
 use std::path::PathBuf;
@@ -10,6 +9,7 @@ use std::process::{Child, Command, Stdio};
 use super::less::retrieve_less_version;
 
 use crate::config;
+use crate::env::DeltaEnv;
 use crate::fatal;
 use crate::features::navigate;
 
@@ -29,34 +29,32 @@ pub enum OutputType {
 
 impl OutputType {
     pub fn from_mode(
+        env: &DeltaEnv,
         mode: PagingMode,
         pager: Option<String>,
         config: &config::Config,
     ) -> Result<Self> {
         use self::PagingMode::*;
         Ok(match mode {
-            Always => OutputType::try_pager(false, pager, config)?,
-            QuitIfOneScreen => OutputType::try_pager(true, pager, config)?,
+            Always => OutputType::try_pager(env, false, pager, config)?,
+            QuitIfOneScreen => OutputType::try_pager(env, true, pager, config)?,
             _ => OutputType::stdout(),
         })
     }
 
     /// Try to launch the pager. Fall back to stdout in case of errors.
     fn try_pager(
+        env: &DeltaEnv,
         quit_if_one_screen: bool,
         pager_from_config: Option<String>,
         config: &config::Config,
     ) -> Result<Self> {
         let mut replace_arguments_to_less = false;
 
-        let pager_from_env = match (
-            env::var("DELTA_PAGER"),
-            env::var("BAT_PAGER"),
-            env::var("PAGER"),
-        ) {
-            (Ok(delta_pager), _, _) => Some(delta_pager),
-            (_, Ok(bat_pager), _) => Some(bat_pager),
-            (_, _, Ok(pager)) => {
+        let pager_from_env = match env.pagers.clone() {
+            (Some(delta_pager), _, _) => Some(delta_pager),
+            (_, Some(bat_pager), _) => Some(bat_pager),
+            (_, _, Some(pager)) => {
                 // less needs to be called with the '-R' option in order to properly interpret ANSI
                 // color sequences. If someone has set PAGER="less -F", we therefore need to
                 // overwrite the arguments and add '-R'.


Test patch:
diff --git a/src/tests/integration_test_utils.rs b/src/tests/integration_test_utils.rs
index 5202492..5d997e3 100644
--- a/src/tests/integration_test_utils.rs
+++ b/src/tests/integration_test_utils.rs
@@ -12,6 +12,7 @@ use crate::ansi;
 use crate::cli;
 use crate::config;
 use crate::delta::delta;
+use crate::env::DeltaEnv;
 use crate::git_config::GitConfig;
 use crate::tests::test_utils;
 use crate::utils::process::tests::FakeParentArgs;
@@ -21,18 +22,35 @@ pub fn make_options_from_args_and_git_config(
     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)
+    _make_options_from_args_and_git_config(
+        DeltaEnv::default(),
+        args,
+        git_config_contents,
+        git_config_path,
+        false,
+    )
 }
 
-pub fn make_options_from_args_and_git_config_honoring_env_var(
+pub fn make_options_from_args_and_git_config_with_custom_env(
+    env: DeltaEnv,
     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)
+    _make_options_from_args_and_git_config(env, args, git_config_contents, git_config_path, false)
+}
+
+pub fn make_options_from_args_and_git_config_honoring_env_var_with_custom_env(
+    env: DeltaEnv,
+    args: &[&str],
+    git_config_contents: Option<&[u8]>,
+    git_config_path: Option<&str>,
+) -> cli::Opt {
+    _make_options_from_args_and_git_config(env, args, git_config_contents, git_config_path, true)
 }
 
 fn _make_options_from_args_and_git_config(
+    env: DeltaEnv,
     args: &[&str],
     git_config_contents: Option<&[u8]>,
     git_config_path: Option<&str>,
@@ -42,13 +60,13 @@ fn _make_options_from_args_and_git_config(
         .map(|s| *s)
         .collect();
     let git_config = match (git_config_contents, git_config_path) {
-        (Some(contents), Some(path)) => Some(make_git_config(contents, path, honor_env_var)),
+        (Some(contents), Some(path)) => Some(make_git_config(&env, contents, path, honor_env_var)),
         _ => {
             args.push("--no-gitconfig");
             None
         }
     };
-    cli::Opt::from_iter_and_git_config(args, git_config)
+    cli::Opt::from_iter_and_git_config(env, args, git_config)
 }
 
 pub fn make_options_from_args(args: &[&str]) -> cli::Opt {
@@ -72,11 +90,16 @@ pub fn make_config_from_args(args: &[&str]) -> config::Config {
     config::Config::from(make_options_from_args(args))
 }
 
-pub fn make_git_config(contents: &[u8], path: &str, honor_env_var: bool) -> GitConfig {
+pub fn make_git_config(
+    env: &DeltaEnv,
+    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, honor_env_var)
+    GitConfig::from_path(env, &path, honor_env_var)
 }
 
 pub fn get_line_of_code_from_delta(
diff --git a/src/tests/test_example_diffs.rs b/src/tests/test_example_diffs.rs
index 2eca792..325f2e9 100644
--- a/src/tests/test_example_diffs.rs
+++ b/src/tests/test_example_diffs.rs
@@ -17,7 +17,6 @@ mod tests {
     }
 
     #[test]
-    #[ignore] // #128
     fn test_added_empty_file() {
         DeltaTest::with_args(&[])
             .with_input(ADDED_EMPTY_FILE)
@@ -1570,6 +1569,21 @@ src/align.rs:71: impl<'a> Alignment<'a> { │
             .expect_contains("Δ src/delta.rs (mode 100700 -> 100644)");
     }
 
+    #[test]
+    fn test_file_deleted_without_preimage() {
+        DeltaTest::with_args(&[])
+            .with_input(GIT_DIFF_FILE_DELETED_WITHOUT_PREIMAGE)
+            .expect_contains("removed: foo.bar");
+    }
+
+    #[test]
+    fn test_files_deleted_without_preimage() {
+        DeltaTest::with_args(&[])
+            .with_input(GIT_DIFF_FILES_DELETED_WITHOUT_PREIMAGE)
+            .expect_contains("removed: foo")
+            .expect_contains("removed: bar");
+    }
+
     #[test]
     fn test_file_mode_change_with_diff() {
         DeltaTest::with_args(&["--navigate", "--keep-plus-minus-markers"])
@@ -2332,6 +2346,23 @@ new mode 100644
 diff --git a/src/delta.rs b/src/delta.rs
 old mode 100700
 new mode 100644
+";
+
+    // This output can be generated with `git diff -D`
+    const GIT_DIFF_FILE_DELETED_WITHOUT_PREIMAGE: &str = "
+diff --git a/foo.bar b/foo.bar
+deleted file mode 100644
+index e019be0..0000000
+";
+
+    // This output can be generated with `git diff -D`
+    const GIT_DIFF_FILES_DELETED_WITHOUT_PREIMAGE: &str = "
+diff --git a/foo b/foo
+deleted file mode 100644
+index e019be0..0000000
+diff --git a/bar b/bar
+deleted file mode 100644
+index e019be0..0000000
 ";
 
     const GIT_DIFF_FILE_MODE_CHANGE_WITH_DIFF: &str = "