Repository: casey/just
Pull request #656 (2020-07-17 04:30:27)

Skip `.env` items which are set in environment
If an environment variable exists with the same key as a variable from a
`.env` file, skip the variable from the `.env` file in favor fo the key
from the environment.

Fixes #656.


Patch:
diff --git a/src/justfile.rs b/src/justfile.rs
index 929555f9..b78a4b47 100644
--- a/src/justfile.rs
+++ b/src/justfile.rs
@@ -149,7 +149,7 @@ impl<'src> Justfile<'src> {
     while let Some((argument, mut tail)) = rest.split_first() {
       if let Some(recipe) = self.get_recipe(argument) {
         if recipe.parameters.is_empty() {
-          grouped.push((recipe, &tail[0..0]));
+          grouped.push((recipe, &[][..]));
         } else {
           let argument_range = recipe.argument_range();
           let argument_count = cmp::min(tail.len(), recipe.max_arguments());
diff --git a/src/lexer.rs b/src/lexer.rs
index bfc3f04d..4141794c 100644
--- a/src/lexer.rs
+++ b/src/lexer.rs
@@ -678,7 +678,7 @@ impl<'src> Lexer<'src> {
       match self.next {
         Some('\'') => break,
         None => return Err(self.error(UnterminatedString)),
-        _ => {},
+        Some(_) => {},
       }
 
       self.advance()?;
diff --git a/src/lib.rs b/src/lib.rs
index db2f44d7..5e72ab71 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,29 +3,28 @@
   clippy::comparison_chain,
   clippy::else_if_without_else,
   clippy::enum_glob_use,
+  clippy::expect_used,
   clippy::filter_map,
   clippy::if_not_else,
   clippy::implicit_return,
   clippy::indexing_slicing,
   clippy::integer_arithmetic,
   clippy::let_underscore_must_use,
+  clippy::map_unwrap_or,
   clippy::match_same_arms,
   clippy::missing_docs_in_private_items,
   clippy::missing_errors_doc,
   clippy::missing_inline_in_public_items,
   clippy::needless_pass_by_value,
   clippy::non_ascii_literal,
-  clippy::option_expect_used,
-  clippy::option_map_unwrap_or,
-  clippy::option_unwrap_used,
   clippy::panic,
   clippy::print_stdout,
-  clippy::result_expect_used,
   clippy::shadow_unrelated,
   clippy::string_add,
   clippy::struct_excessive_bools,
   clippy::too_many_lines,
   clippy::unreachable,
+  clippy::unwrap_used,
   clippy::use_debug,
   clippy::wildcard_enum_match_arm,
   clippy::wildcard_imports
diff --git a/src/load_dotenv.rs b/src/load_dotenv.rs
index abe062c4..0702f49c 100644
--- a/src/load_dotenv.rs
+++ b/src/load_dotenv.rs
@@ -6,8 +6,14 @@ pub(crate) fn load_dotenv() -> RunResult<'static, BTreeMap<String, String>> {
   #![allow(deprecated)]
   match dotenv::dotenv_iter() {
     Ok(iter) => {
-      let result: dotenv::Result<BTreeMap<String, String>> = iter.collect();
-      result.map_err(|dotenv_error| RuntimeError::Dotenv { dotenv_error })
+      let mut dotenv = BTreeMap::new();
+      for result in iter {
+        let (key, value) = result.map_err(|dotenv_error| RuntimeError::Dotenv { dotenv_error })?;
+        if env::var_os(&key).is_none() {
+          dotenv.insert(key, value);
+        }
+      }
+      Ok(dotenv)
     },
     Err(dotenv_error) =>
       if dotenv_error.not_found() {
diff --git a/src/tree.rs b/src/tree.rs
index 9ddfa617..b71c6edb 100644
--- a/src/tree.rs
+++ b/src/tree.rs
@@ -101,8 +101,7 @@ impl<'text> Tree<'text> {
 
   /// Like `push`, but modify self in-place
   pub(crate) fn push_mut(&mut self, tree: impl Into<Tree<'text>>) {
-    let tree = mem::replace(self, Tree::List(Vec::new())).push(tree.into());
-    mem::replace(self, tree);
+    *self = mem::replace(self, Tree::List(Vec::new())).push(tree.into());
   }
 }
 


Test patch:
diff --git a/tests/integration.rs b/tests/integration.rs
index 6eccdbd1..b40cd04f 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -1,4 +1,5 @@
 use std::{
+  collections::BTreeMap,
   env, fs,
   io::Write,
   path::Path,
@@ -16,6 +17,9 @@ macro_rules! test {
     name:     $name:ident,
     justfile: $justfile:expr,
     $(args:     ($($arg:tt)*),)?
+    $(env:      {
+      $($env_key:literal : $env_value:literal,)*
+    },)?
     $(stdin:    $stdin:expr,)?
     $(stdout:   $stdout:expr,)?
     $(stderr:   $stderr:expr,)?
@@ -24,6 +28,11 @@ macro_rules! test {
   ) => {
     #[test]
     fn $name() {
+      #[allow(unused_mut)]
+      let mut env = BTreeMap::new();
+
+      $($(env.insert($env_key.to_string(), $env_value.to_string());)*)?
+
       Test {
         justfile: $justfile,
         $(args: &[$($arg)*],)?
@@ -32,6 +41,7 @@ macro_rules! test {
         $(stderr: $stderr,)?
         $(status: $status,)?
         $(shell: $shell,)?
+        env,
         ..Test::default()
       }.run();
     }
@@ -41,6 +51,7 @@ macro_rules! test {
 struct Test<'a> {
   justfile: &'a str,
   args:     &'a [&'a str],
+  env:      BTreeMap<String, String>,
   stdin:    &'a str,
   stdout:   &'a str,
   stderr:   &'a str,
@@ -53,6 +64,7 @@ impl<'a> Default for Test<'a> {
     Test {
       justfile: "",
       args:     &[],
+      env:      BTreeMap::new(),
       stdin:    "",
       stdout:   "",
       stderr:   "",
@@ -86,6 +98,7 @@ impl<'a> Test<'a> {
 
     let mut child = command
       .args(self.args)
+      .envs(self.env)
       .current_dir(tmp.path())
       .stdin(Stdio::piped())
       .stdout(Stdio::piped())
@@ -2070,6 +2083,18 @@ echo:
    stderr:   "echo DEFAULT\n",
 }
 
+test! {
+   name:     dotenv_env_var_override,
+   justfile: "
+#
+echo:
+  echo $DOTENV_KEY
+ ",
+   env:      {"DOTENV_KEY": "not-the-dotenv-value",},
+   stdout:   "not-the-dotenv-value\n",
+   stderr:   "echo $DOTENV_KEY\n",
+}
+
 test! {
    name:     invalid_escape_sequence_message,
    justfile: r#"