Repository: xonsh/xonsh

Issue:
source-bash return code will always be 0
Even if the file doesn't exist.



Discussion:

Resolving patch:
diff --git a/xonsh/aliases.py b/xonsh/aliases.py
--- a/xonsh/aliases.py
+++ b/xonsh/aliases.py
@@ -218,6 +218,9 @@ def source_foreign(args, stdin=None):
                                           funcscmd=ns.funcscmd,
                                           sourcer=ns.sourcer,
                                           use_tmpfile=ns.use_tmpfile)
+    if fsenv is None:
+        return (None, 'xonsh: error: Source failed: '
+                      '{}\n'.format(ns.prevcmd), 1)
     # apply results
     env = builtins.__xonsh_env__
     denv = env.detype()
@@ -225,12 +228,11 @@ def source_foreign(args, stdin=None):
         if k in denv and v == denv[k]:
             continue  # no change from original
         env[k] = v
-    # If run in un-safe mode we are sure the command completed correctly,
-    # thus we can remove any env-vars that were unset by the script.
-    if not ns.safe:
-        for k in denv:
-            if k not in fsenv:
-                env.pop(k, None)
+    # Remove any env-vars that were unset by the script.
+    for k in denv:
+        if k not in fsenv:
+            env.pop(k, None)
+    # Update aliases
     baliases = builtins.aliases
     for k, v in fsaliases.items():
         if k in baliases and v == baliases[k]:
@@ -255,7 +257,7 @@ def source_cmd(args, stdin=None):
     fpath = locate_binary(args[0])
     args[0] = fpath if fpath else args[0]
     if not os.path.isfile(args[0]):
-        raise FileNotFoundError(args[0])
+        return (None, 'xonsh: error: File not found: {}\n'.format(args[0]), 1)
     prevcmd = 'call '
     prevcmd += ' '.join([argvquote(arg, force=True) for arg in args])
     prevcmd = escape_windows_cmd_string(prevcmd)
@@ -265,7 +267,7 @@ def source_cmd(args, stdin=None):
     args.append('--interactive=0')
     args.append('--sourcer=call')
     args.append('--envcmd=set')
-    args.append('--safe=0')
+    args.append('--postcmd=if errorlevel 1 exit 1')
     args.append('--use-tmpfile=1')
     return source_foreign(args, stdin=stdin)
 
@@ -280,9 +282,10 @@ def xexec(args, stdin=None):
         try:
             os.execvpe(args[0], args, denv)
         except FileNotFoundError as e:
-            return 'xonsh: ' + e.args[1] + ': ' + args[0] + '\n'
+            return (None, 'xonsh: exec: file not found: {}: {}'
+                          '\n'.format(e.args[1], args[0]), 1)
     else:
-        return 'xonsh: exec: no args specified\n'
+        return (None, 'xonsh: exec: no args specified\n', 1)
 
 
 _BANG_N_PARSER = None
diff --git a/xonsh/foreign_shells.py b/xonsh/foreign_shells.py
--- a/xonsh/foreign_shells.py
+++ b/xonsh/foreign_shells.py
@@ -225,7 +225,7 @@ def foreign_shell_data(shell, interactive=True, login=False, envcmd=None,
     except (subprocess.CalledProcessError, FileNotFoundError):
         if not safe:
             raise
-        return {}, {}
+        return None, None
     finally:
         if use_tmpfile:
             os.remove(tmpfile.name)
@@ -474,7 +474,8 @@ def load_foreign_envs(shells=None, config=None, issue_warning=True):
     for shell in shells:
         shell = ensure_shell(shell)
         shenv, _ = foreign_shell_data(**shell)
-        env.update(shenv)
+        if shenv:
+            env.update(shenv)
     return env
 
 
@@ -503,5 +504,6 @@ def load_foreign_aliases(shells=None, config=None, issue_warning=True):
     for shell in shells:
         shell = ensure_shell(shell)
         _, shaliases = foreign_shell_data(**shell)
-        aliases.update(shaliases)
+        if shaliases:
+            aliases.update(shaliases)
     return aliases


Test patch: