1
0
mirror of https://github.com/Tarrasch/zsh-autoenv.git synced 2024-11-22 15:30:59 +02:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Joe Block 2015-02-18 07:34:08 -08:00
commit a524c58d32
19 changed files with 1385 additions and 104 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tests/*.err

View File

@ -4,4 +4,4 @@ python:
before_script:
- "sudo apt-get install zsh"
install: "sudo pip install cram"
script: "make tests"
script: "make test_full"

View File

@ -1,7 +1,34 @@
.PHONY: itests tests
# Default, can be overridden using "make test ZDOTDIR=...".
ZDOTDIR:=${CURDIR}/tests/ZDOTDIR
itests:
ZDOTDIR="${PWD}/tests" cram -i --shell=zsh tests
# Export it, and make it absolute.
override export ZDOTDIR:=$(abspath $(ZDOTDIR))
tests:
ZDOTDIR="${PWD}/tests" cram --shell=zsh tests
test:
cram --shell=zsh -v tests
itest:
cram -i --shell=zsh tests
# Run tests with all ZDOTDIRs.
test_full:
for i in $(wildcard tests/ZDOTDIR*); do \
echo "ZDOTDIR=$$i"; \
ZDOTDIR=${CURDIR}/$$i cram --shell=zsh -v tests; \
echo; \
done
# Define targets for test files, with relative and abolute path.
# Use verbose output, which is useful with Vim's 'errorformat'.
TESTS:=$(wildcard tests/*.t)
uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1)))
_TESTS_REL_AND_ABS:=$(call uniq,$(abspath $(TESTS)) $(TESTS))
$(_TESTS_REL_AND_ABS):
cram --shell=zsh -v $@
.PHONY: $(_TESTS_REL_AND_ABS)
.PHONY: itest test
clean:
$(RM) tests/*.err

View File

@ -1,6 +1,6 @@
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/Tarrasch/zsh-autoenv/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
[![Build Status](https://travis-ci.org/Tarrasch/zsh-autoenv.png)](https://travis-ci.org/Tarrasch/zsh-autoenv)
[![Build Status](https://travis-ci.org/Tarrasch/zsh-autoenv.svg?branch=master)](https://travis-ci.org/Tarrasch/zsh-autoenv)
# Autoenv for zsh
@ -26,6 +26,13 @@ Add
zgen load Tarrasch/zsh-autoenv
to your `.zshrc` where you're loading your other plugins.
### Manually
Clone the repository and source it from your `~/.zshrc` file:
git clone https://github.com/Tarrasch/zsh-autoenv ~/.dotfiles/lib/zsh-autoenv
echo 'source ~/.dotfiles/lib/zsh-autoenv/autoenv.zsh' >> ~/.zshrc
>>>>>>> upstream/master
## Credits

View File

@ -1,70 +1,350 @@
# Stolen from
# Initially based on
# https://github.com/joshuaclayton/dotfiles/blob/master/zsh_profile.d/autoenv.zsh
export ENV_AUTHORIZATION_FILE=$HOME/.env_auth
export AUTOENV_ENV_FILENAME=$HOME/.env_auth
[ -e $AUTOENV_ENV_FILENAME ] || touch $AUTOENV_ENV_FILENAME
_dotenv_hash_pair() {
env_file=$1
env_shasum=$(shasum $env_file | cut -d' ' -f1)
echo "$env_file:$env_shasum"
}
# Name of file to look for when entering directories.
: ${AUTOENV_FILE_ENTER:=.env}
_dotenv_authorized_env_file() {
env_file=$1
pair=$(_dotenv_hash_pair $env_file)
touch $ENV_AUTHORIZATION_FILE
\grep -Gq $pair $ENV_AUTHORIZATION_FILE
}
# Name of file to look for when leaving directories.
# Requires AUTOENV_HANDLE_LEAVE=1.
: ${AUTOENV_FILE_LEAVE:=.env.leave}
_dotenv_authorize() {
env_file=$1
_dotenv_deauthorize $env_file
_dotenv_hash_pair $env_file >> $ENV_AUTHORIZATION_FILE
}
# Look for .env in parent dirs?
: ${AUTOENV_LOOK_UPWARDS:=1}
_dotenv_deauthorize() {
env_file=$1
echo $(grep -Gv $env_file $ENV_AUTHORIZATION_FILE) > $ENV_AUTHORIZATION_FILE
}
# Handle leave events when changing away from a subtree, where an "enter"
# event was handled?
: ${AUTOENV_HANDLE_LEAVE:=1}
_dotenv_print_unauthorized_message() {
echo "Attempting to load unauthorized env: $1"
echo ""
echo "**********************************************"
echo ""
cat $1
echo ""
echo "**********************************************"
echo ""
echo "Would you like to authorize it? (y/n)"
}
# Enable debugging. Multiple levels are supported (max 2).
: ${AUTOENV_DEBUG:=0}
# This function can be mocked in tests
_dotenv_read_answer() {
read answer
}
# (Temporarily) disable zsh-autoenv. This gets looked at in the chpwd handler.
: ${AUTOENV_DISABLED:=0}
_dotenv_source_env() {
local env_file="$PWD/.env"
# Public helper functions, which can be used from your .env files:
#
# Source the next .env file from parent directories.
# This is useful if you want to use a base .env file for a directory subtree.
autoenv_source_parent() {
local parent_env_file=$(_autoenv_get_file_upwards ${autoenv_env_file:h})
if [[ -f $env_file ]]
then
if _dotenv_authorized_env_file $env_file
then
source $env_file
return 0
fi
_dotenv_print_unauthorized_message $env_file
_dotenv_read_answer
if [[ $answer == 'y' ]]
then
_dotenv_authorize $env_file
source $env_file
fi
if [[ -n $parent_env_file ]] \
&& _autoenv_check_authorized_env_file $parent_env_file; then
_autoenv_debug "Calling autoenv_source_parent: parent_env_file:$parent_env_file"
_autoenv_source $parent_env_file enter
fi
}
chpwd_functions=($chpwd_functions _dotenv_source_env)
# Internal functions. {{{
# Internal: stack of entered (and handled) directories. {{{
typeset -g -a _autoenv_stack_entered
_autoenv_stack_entered=()
# -g: make it global, this is required when used with antigen.
typeset -g -A _autoenv_stack_entered_mtime
_autoenv_stack_entered_mtime=()
# Add an entry to the stack, and remember its mtime.
_autoenv_stack_entered_add() {
local env_file=$1
# Remove any existing entry.
_autoenv_stack_entered_remove $env_file
_autoenv_debug "[stack] adding: $env_file" 2
# Append it to the stack, and remember its mtime.
_autoenv_stack_entered+=($env_file)
_autoenv_stack_entered_mtime[$env_file]=$(_autoenv_get_file_mtime $env_file)
}
# zstat_mime helper, conditionally defined.
# Load zstat module, but only its builtin `zstat`.
if ! zmodload -F zsh/stat b:zstat 2>/dev/null; then
# If the module is not available, define a wrapper around `stat`, and use its
# terse output instead of format, which is not supported by busybox.
# Assume '+mtime' as $1.
_autoenv_get_file_mtime() {
setopt localoptions pipefail
stat -t $1 2>/dev/null | cut -f13 -d ' ' || echo 0
}
else
_autoenv_get_file_mtime() {
zstat +mtime $1 2>/dev/null || echo 0
}
fi
# Remove an entry from the stack.
_autoenv_stack_entered_remove() {
local env_file=$1
_autoenv_debug "[stack] removing: $env_file" 2
_autoenv_stack_entered[$_autoenv_stack_entered[(i)$env_file]]=()
_autoenv_stack_entered_mtime[$env_file]=
}
# Is the given entry already in the stack?
# This checks for the env_file ($1) as-is and with symlinks resolved.
_autoenv_stack_entered_contains() {
local env_file=$1
local f i
if (( ${+_autoenv_stack_entered[(r)${env_file}]} )); then
# Entry is in stack.
f=$env_file
else
for i in $_autoenv_stack_entered; do
if [[ ${i:A} == ${env_file:A} ]]; then
# Entry is in stack (compared with resolved symlinks).
f=$i
break
fi
done
fi
if [[ -n $f ]]; then
if [[ $_autoenv_stack_entered_mtime[$f] == $(_autoenv_get_file_mtime $f) ]]; then
# Entry has the expected mtime.
return
fi
fi
return 1
}
# }}}
# Internal function for debug output. {{{
_autoenv_debug() {
local msg=$1
local level=${2:-1}
if [[ $AUTOENV_DEBUG -lt $level ]]; then
return
fi
# Load zsh color support.
if [[ -z $color ]]; then
autoload colors
colors
fi
# Build $indent prefix.
local indent=
if [[ $_autoenv_debug_indent -gt 0 ]]; then
for i in {1..${_autoenv_debug_indent}}; do
indent=" $indent"
done
fi
# Split $msg by \n (not newline).
lines=(${(ps:\\n:)msg})
for line in $lines; do
echo -n "${fg_bold[blue]}[autoenv]${fg_no_bold[default]} " >&2
echo ${indent}${line} >&2
done
}
# }}}
# Generate hash pair for a given file ($1).
# A fixed hash value can be given as 2nd arg, but is used with tests only.
_autoenv_hash_pair() {
local env_file=${1:A}
local env_shasum=${2:-}
if [[ -z $env_shasum ]]; then
if ! [[ -e $env_file ]]; then
echo "Missing file argument for _autoenv_hash_pair!" >&2
return 1
fi
env_shasum=$(shasum $env_file | cut -d' ' -f1)
fi
echo ":${env_file}:${env_shasum}:1"
}
_autoenv_authorized_env_file() {
local env_file=$1
local pair=$(_autoenv_hash_pair $env_file)
test -f $AUTOENV_ENV_FILENAME \
&& \grep -qF $pair $AUTOENV_ENV_FILENAME
}
_autoenv_authorize() {
local env_file=${1:A}
_autoenv_deauthorize $env_file
_autoenv_hash_pair $env_file >>| $AUTOENV_ENV_FILENAME
}
# Deauthorize a given filename, by removing it from the auth file.
# This uses `test -s` to only handle non-empty files, and a subshell to
# allow for writing to the same file again.
_autoenv_deauthorize() {
local env_file=${1:A}
if [[ -s $AUTOENV_ENV_FILENAME ]]; then
echo "$(\grep -vF :${env_file}: $AUTOENV_ENV_FILENAME)" >| $AUTOENV_ENV_FILENAME
fi
}
# This function can be mocked in tests
_autoenv_ask_for_yes() {
local answer
read answer
if [[ $answer == "yes" ]]; then
return 0
else
return 1
fi
}
# Args: 1: absolute path to env file (resolved symlinks).
_autoenv_check_authorized_env_file() {
if ! [[ -f $1 ]]; then
return 1
fi
if ! _autoenv_authorized_env_file $1; then
echo "Attempting to load unauthorized env file!"
command ls -l $1
echo ""
echo "**********************************************"
echo ""
cat $1
echo ""
echo "**********************************************"
echo ""
echo -n "Would you like to authorize it? (type 'yes') "
if ! _autoenv_ask_for_yes; then
return 1
fi
_autoenv_authorize $1
fi
return 0
}
# Get directory of this file (absolute, with resolved symlinks).
_autoenv_source_dir=${0:A:h}
_autoenv_source() {
local env_file=$1
autoenv_event=$2
local _autoenv_envfile_dir=${3:-${1:A:h}}
autoenv_from_dir=$_autoenv_chpwd_prev_dir
autoenv_to_dir=$PWD
autoenv_env_file=$env_file
# Source varstash library once.
if [[ -z "$functions[(I)autostash]" ]]; then
source $_autoenv_source_dir/lib/varstash
# NOTE: Varstash uses $PWD as default for varstash_dir, we might set it to
# ${env_file:h}.
fi
# Change to directory of env file, source it and cd back.
local new_dir=$PWD
_autoenv_debug "== SOURCE: ${bold_color:-}$env_file${reset_color:-}\n PWD: $PWD"
(( _autoenv_debug_indent++ ))
source $env_file
(( _autoenv_debug_indent-- ))
_autoenv_debug "== END SOURCE =="
if [[ $autoenv_event == enter ]]; then
_autoenv_stack_entered_add $env_file
fi
# Unset vars set for enter/leave scripts.
# This should not get done for recursion (via autoenv_source_parent),
# and can be useful to have in general after autoenv was used.
# unset autoenv_event autoenv_from_dir autoenv_to_dir autoenv_env_file
}
_autoenv_get_file_upwards() {
local look_from=${1:-$PWD}
local look_for=${2:-$AUTOENV_FILE_ENTER}
# Manually look in parent dirs. An extended Zsh glob should use Y1 for
# performance reasons, which is only available in zsh-5.0.5-146-g9381bb6.
local last
local parent_dir="$look_from/.."
while true; do
parent_dir=${parent_dir:A}
if [[ $parent_dir == $last ]]; then
break
fi
parent_file="${parent_dir}/${look_for}"
if [[ -f $parent_file ]]; then
echo $parent_file
break
fi
last=$parent_dir
parent_dir="${parent_dir}/.."
done
}
_autoenv_chpwd_prev_dir=$PWD
_autoenv_chpwd_handler() {
_autoenv_debug "Calling chpwd handler: PWD=$PWD"
if (( $AUTOENV_DISABLED )); then
_autoenv_debug "Disabled (AUTOENV_DISABLED)."
return
fi
local env_file="$PWD/$AUTOENV_FILE_ENTER"
_autoenv_debug "env_file: $env_file"
# Handle leave event for previously sourced env files.
if [[ $AUTOENV_HANDLE_LEAVE == 1 ]] && (( $#_autoenv_stack_entered )); then
local prev_file prev_dir
for prev_file in ${_autoenv_stack_entered}; do
prev_dir=${prev_file:h}
if ! [[ ${PWD}/ == ${prev_dir}/* ]]; then
local env_file_leave=$prev_dir/$AUTOENV_FILE_LEAVE
if _autoenv_check_authorized_env_file $env_file_leave; then
_autoenv_source $env_file_leave leave $prev_dir
fi
# Unstash any autostashed stuff.
varstash_dir=$prev_dir autounstash
_autoenv_stack_entered_remove $prev_file
fi
done
fi
if ! [[ -f $env_file ]] && [[ $AUTOENV_LOOK_UPWARDS == 1 ]]; then
env_file=$(_autoenv_get_file_upwards $PWD)
if [[ -z $env_file ]]; then
_autoenv_chpwd_prev_dir=$PWD
return
fi
fi
# Load the env file only once: check if $env_file is in the stack of entered
# directories.
if _autoenv_stack_entered_contains $env_file; then
_autoenv_debug "Already in stack: $env_file"
_autoenv_chpwd_prev_dir=$PWD
return
fi
if ! _autoenv_check_authorized_env_file $env_file; then
_autoenv_chpwd_prev_dir=$PWD
return
fi
# Source the enter env file.
_autoenv_debug "Sourcing from chpwd handler: $env_file"
_autoenv_source $env_file enter
_autoenv_chpwd_prev_dir=$PWD
(( _autoenv_debug_indent++ ))
}
# }}}
autoload -U add-zsh-hook
add-zsh-hook chpwd _autoenv_chpwd_handler
# Look in current directory already.
_autoenv_chpwd_handler

1
init.zsh Normal file
View File

@ -0,0 +1 @@
. $(dirname $0)/autoenv.zsh

344
lib/varstash Normal file
View File

@ -0,0 +1,344 @@
################################################################################
# Stash/unstash support for per-directory variables
#
# Adopted for zsh-autoenv.
#
# Copyright (c) 2009,2012 Dave Olszewski <cxreg@pobox.com>
# http://github.com/cxreg/smartcd
#
# This code is released under GPL v2 and the Artistic License, and
# may be redistributed under the terms of either.
#
#
# This library allows you to save the current value of a given environment
# variable in a temporary location, so that you can modify it, and then
# later restore its original value.
#
# Note that you will need to be in the same directory you were in when you
# stashed in order to successfully unstash. This is because the temporary
# variable is derived from your current working directory's path.
#
# Usage:
# stash PATH
# export PATH=/something/else
# [...]
# unstash PATH
#
# Note that this was written for use with, and works very well with,
# smartcd. See the documentation there for examples.
#
# An alternate usage is `autostash' which will trigger autounstash when
# leaving the directory, if combined with smartcd. This reduces the amount
# of explicit configuration you need to provide:
#
# autostash PATH
# export PATH=/something/else
#
# You may also do both operations on line line, leaving only the very succinct
#
# autostash PATH=/something/else
#
# If you attempt to stash the same value twice, a warning will be displayed
# and the second stash will not occur. To make it happen anyway, pass -f
# as the first argument to stash.
#
# $ stash FOO
# $ stash FOO
# You have already stashed FOO, please specify "-f" if you want to overwrite another stashed value
# $ stash -f FOO
# $
#
# This rule is a bit different if you are assigning a value and the variable
# has already been stashed. In that case, the new value will be assigned, but
# the stash will not be overwritten. This allows for non-conflicting chained
# stash-assign rules.
#
################################################################################
# Library functions, from smartcd's lib/core/arrays. {{{
function apush() {
local var=$1; shift
eval "$var=(\${$var[@]} \"\$@\")"
}
function alen() {
local var=$1
if [[ -n $var ]]; then
eval "echo \${#$var[@]}"
fi
}
function afirst() {
setopt localoptions && setopt ksharrays
local var=$1
if [[ -n $var ]] && (( $(eval "echo \${#$var[@]}") >= 1 )); then
eval "echo \"\${$var""[0]}\""
fi
}
function ashift() {
setopt localoptions && setopt ksharrays
local var=$1
local _ashift_return=
if [[ -n $var ]] && (( $(eval "echo \${#$var[@]}") >= 1 )); then
eval "_ashift_return=\"\${$var""[0]}\""
eval "$var""[0]=()"
echo "$_ashift_return"
fi
}
# }}}
function stash() {
if [[ $1 == "-f" ]]; then
local force=1; shift
fi
while [[ -n $1 ]]; do
if [[ $1 == "alias" && $2 == *=* ]]; then
shift
local _stashing_alias_assign=1
continue
fi
local stash_expression=$1
local stash_which=${stash_expression%%'='*}
local stash_name=$(_mangle_var $stash_which)
# Extract the value and make it double-quote safe
local stash_value=${stash_expression#*'='}
stash_value=${stash_value//\\/\\\\}
stash_value=${stash_value//\"/\\\"}
stash_value=${stash_value//\`/\\\`}
stash_value=${stash_value//\$/\\\$}
if [[ ( -n "$(eval echo '$__varstash_alias__'$stash_name)" ||
-n "$(eval echo '$__varstash_function__'$stash_name)" ||
-n "$(eval echo '$__varstash_array__'$stash_name)" ||
-n "$(eval echo '$__varstash_export__'$stash_name)" ||
-n "$(eval echo '$__varstash_variable__'$stash_name)" ||
-n "$(eval echo '$__varstash_nostash__'$stash_name)" )
&& -z $force ]]; then
if [[ -z $already_stashed && ${already_stashed-_} == "_" ]]; then
local already_stashed=1
else
already_stashed=1
fi
if [[ $stash_which == $stash_expression ]]; then
if [[ -z $run_from_smartcd ]]; then
echo "You have already stashed $stash_which, please specify \"-f\" if you want to overwrite another stashed value"
fi
# Skip remaining work if we're not doing an assignment
shift
continue
fi
fi
# Handle any alias that may exist under this name
if [[ -z $already_stashed ]]; then
local alias_def="$(eval alias $stash_which 2>/dev/null)"
if [[ -n $alias_def ]]; then
alias_def=${alias_def#alias }
eval "__varstash_alias__$stash_name=\"$alias_def\""
local stashed=1
fi
fi
if [[ $stash_which != $stash_expression && -n $_stashing_alias_assign ]]; then
eval "alias $stash_which=\"$stash_value\""
fi
# Handle any function that may exist under this name
if [[ -z $already_stashed ]]; then
local function_def="$(declare -f $stash_which)"
if [[ -n $function_def ]]; then
# make function definition quote-safe. because we are going to evaluate the
# source with "echo -e", we need to double-escape the backslashes (so 1 -> 4)
function_def=${function_def//\\/\\\\\\\\}
function_def=${function_def//\"/\\\"}
function_def=${function_def//\`/\\\`}
function_def=${function_def//\$/\\\$}
eval "__varstash_function__$stash_name=\"$function_def\""
local stashed=1
fi
fi
# Handle any variable that may exist under this name
local vartype="$(declare -p $stash_which 2>/dev/null)"
if [[ -n $vartype ]]; then
if [[ -n $ZSH_VERSION ]]; then
local pattern="typeset"
else
local pattern="declare"
fi
if [[ $vartype == $pattern" -a"* ]]; then
# varible is an array
if [[ -z $already_stashed ]]; then
eval "__varstash_array__$stash_name=(\"\${$stash_which""[@]}\")"
fi
elif [[ $vartype == $pattern" -x"* ]]; then
# variable is exported
if [[ -z $already_stashed ]]; then
eval "__varstash_export__$stash_name=\"\$$stash_which\""
fi
if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then
eval "export $stash_which=\"$stash_value\""
fi
else
# regular variable
if [[ -z $already_stashed ]]; then
eval "__varstash_variable__$stash_name=\"\$$stash_which\""
fi
if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then
eval "$stash_which=\"$stash_value\""
fi
fi
local stashed=1
fi
if [[ -z $stashed ]]; then
# Nothing in the variable we're stashing, but make a note that we stashed so we
# do the right thing when unstashing. Without this, we take no action on unstash
# Zsh bug sometimes caues
# (eval):1: command not found: __varstash_nostash___tmp__home_dolszewski_src_smartcd_RANDOM_VARIABLE=1
# fixed in zsh commit 724fd07a67f, version 4.3.14
if [[ -z $already_stashed ]]; then
eval "__varstash_nostash__$stash_name=1"
fi
# In the case of a previously unset variable that we're assigning too, export it
if [[ $stash_which != $stash_expression && -z $_stashing_alias_assign ]]; then
eval "export $stash_which=\"$stash_value\""
fi
fi
shift
unset -v _stashing_alias_assign
done
}
function autostash() {
local run_from_autostash=1
while [[ -n $1 ]]; do
if [[ $1 == "alias" && $2 == *=* ]]; then
shift
local _stashing_alias_assign=1
fi
local already_stashed=
stash "$1"
if [[ -z $already_stashed ]]; then
local autostash_name=$(_mangle_var AUTOSTASH)
local varname=${1%%'='*}
apush $autostash_name "$varname"
fi
shift
unset -v _stashing_alias_assign
done
}
function unstash() {
while [[ -n $1 ]]; do
local unstash_which=$1
if [[ -z $unstash_which ]]; then
continue
fi
local unstash_name=$(_mangle_var $unstash_which)
# This bit is a little tricky. Here are the rules:
# 1) unstash any alias, function, or variable which matches
# 2) if one or more matches, but not all, delete any that did not
# 3) if none match but nostash is found, delete all
# 4) if none match and nostash not found, do nothing
# Unstash any alias
if [[ -n "$(eval echo \$__varstash_alias__$unstash_name)" ]]; then
eval "alias $(eval echo \$__varstash_alias__$unstash_name)"
unset __varstash_alias__$unstash_name
local unstashed=1
local unstashed_alias=1
fi
# Unstash any function
if [[ -n "$(eval echo \$__varstash_function__$unstash_name)" ]]; then
eval "function $(eval echo -e \"\$__varstash_function__$unstash_name\")"
unset __varstash_function__$unstash_name
local unstashed=1
local unstashed_function=1
fi
# Unstash any variable
if [[ -n "$(declare -p __varstash_array__$unstash_name 2>/dev/null)" ]]; then
eval "$unstash_which=(\"\${__varstash_array__$unstash_name""[@]}\")"
unset __varstash_array__$unstash_name
local unstashed=1
local unstashed_variable=1
elif [[ -n "$(declare -p __varstash_export__$unstash_name 2>/dev/null)" ]]; then
eval "export $unstash_which=\"\$__varstash_export__$unstash_name\""
unset __varstash_export__$unstash_name
local unstashed=1
local unstashed_variable=1
elif [[ -n "$(declare -p __varstash_variable__$unstash_name 2>/dev/null)" ]]; then
# Unset variable first to reset export
unset -v $unstash_which
eval "$unstash_which=\"\$__varstash_variable__$unstash_name\""
unset __varstash_variable__$unstash_name
local unstashed=1
local unstashed_variable=1
fi
# Unset any values which did not exist at time of stash
local nostash="$(eval echo \$__varstash_nostash__$unstash_name)"
unset __varstash_nostash__$unstash_name
if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_alias" ) ]]; then
unalias $unstash_which 2>/dev/null
fi
if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_function" ) ]]; then
unset -f $unstash_which 2>/dev/null
fi
if [[ ( -n "$nostash" && -z "$unstashed" ) || ( -n "$unstashed" && -z "$unstashed_variable" ) ]]; then
# Don't try to unset illegal variable names
# Using substitution to avoid using regex, which might fail to load on Zsh (minimal system).
if [[ ${unstash_which//[^a-zA-Z0-9_]/} == $unstash_which && $unstash_which != [0-9]* ]]; then
unset -v $unstash_which
fi
fi
shift
done
}
function autounstash() {
# If there is anything in (mangled) variable AUTOSTASH, then unstash it
local autounstash_name=$(_mangle_var AUTOSTASH)
if (( $(alen $autounstash_name) > 0 )); then
local run_from_autounstash=1
while (( $(alen $autounstash_name) > 0 )); do
local autounstash_var=$(afirst $autounstash_name)
ashift $autounstash_name >/dev/null
unstash $autounstash_var
done
unset $autounstash_name
fi
}
function _mangle_var() {
local mangle_var_where="${varstash_dir:-$PWD}"
mangle_var_where=${mangle_var_where//[^A-Za-z0-9]/_}
local mangled_name=${1//[^A-Za-z0-9]/_}
echo "_tmp_${mangle_var_where}_${mangled_name}"
}
# vim: filetype=sh autoindent expandtab shiftwidth=4 softtabstop=4

View File

@ -0,0 +1,3 @@
source ${ZDOTDIR}/../ZDOTDIR/.zshenv
setopt noclobber

View File

@ -0,0 +1,13 @@
# Use invalid module path for zsh, to test alternative zstat implementation.
# Pre-load zsh/parameter, where we do not have/need(?) an alternative
# implementation.
zmodload zsh/parameter
module_path=(/dev/null)
zstat() {
echo "Should not get called."
}
source ${ZDOTDIR}/../ZDOTDIR/.zshenv

View File

@ -0,0 +1,12 @@
test -f "$TESTDIR/.zcompdump" && rm "$TESTDIR/.zcompdump"
AUTOENV_DEBUG=0
antigen-like-loader-function() {
source "$TESTDIR/../autoenv.plugin.zsh"
}
antigen-like-loader-function
export AUTOENV_ENV_FILENAME="$PWD/.env_auth"
echo -n > $AUTOENV_ENV_FILENAME

View File

@ -1,4 +1,8 @@
test -f "$TESTDIR/.zcompdump" && rm "$TESTDIR/.zcompdump"
AUTOENV_DEBUG=0
source "$TESTDIR/../autoenv.plugin.zsh"
export ENV_AUTHORIZATION_FILE="$PWD/.env_auth"
export AUTOENV_ENV_FILENAME="$PWD/.env_auth"
echo -n > $AUTOENV_ENV_FILENAME

60
tests/_autoenv_stack.t Normal file
View File

@ -0,0 +1,60 @@
Tests for internal stack handling.
$ source $TESTDIR/setup.sh || return 1
Non-existing entries are allowed and handled without error.
$ _autoenv_stack_entered_add non-existing
$ echo $_autoenv_stack_entered
non-existing
Add existing entries.
$ mkdir -p sub/sub2
$ touch -t 201401010101 sub/file
$ touch -t 201401010102 sub
$ touch -t 201401010103 sub/sub2
$ _autoenv_stack_entered_add sub
$ _autoenv_stack_entered_add sub/file
$ _autoenv_stack_entered_add sub/sub2
$ echo $_autoenv_stack_entered
non-existing sub sub/file sub/sub2
$ _autoenv_stack_entered_add non-existing
$ echo $_autoenv_stack_entered
sub sub/file sub/sub2 non-existing
$ echo ${(k)_autoenv_stack_entered}
sub sub/file sub/sub2 non-existing
$ echo $_autoenv_stack_entered_mtime
1388538180 1388538060 1388538120 0
Touch the file and re-add it.
$ touch -t 201401012359 sub/file
$ _autoenv_stack_entered_add sub/file
The mtime should have been updated.
$ echo ${_autoenv_stack_entered_mtime[sub/file]}
1388620740
It should have moved to the end of the stack.
$ echo ${(k)_autoenv_stack_entered}
sub sub/sub2 non-existing sub/file
Test lookup of containing elements.
$ _autoenv_stack_entered_contains sub/file
$ _autoenv_stack_entered_contains non-existing
$ _autoenv_stack_entered_contains not-added
[1]
Test removing.
$ _autoenv_stack_entered_remove sub
$ echo ${_autoenv_stack_entered}
sub/sub2 non-existing sub/file

87
tests/_autoenv_utils.t Normal file
View File

@ -0,0 +1,87 @@
Tests for internal util methods.
$ source $TESTDIR/setup.sh || return 1
Non-existing entries are allowed and handled without error.
$ mkdir -p sub/sub2
$ touch file sub/file sub/sub2/file
Should not get the file from the current dir.
$ _autoenv_get_file_upwards . file
$ cd sub/sub2
$ _autoenv_get_file_upwards . file
*/_autoenv_utils.t/sub/file (glob)
Tests for _autoenv_authorize. {{{
Auth file is empty.
$ cd ../..
$ cat $AUTOENV_ENV_FILENAME
Failed authorization should keep the auth file empty.
$ _autoenv_authorize does-not-exist
Missing file argument for _autoenv_hash_pair!
[1]
$ cat $AUTOENV_ENV_FILENAME
Now adding some auth pair.
$ echo first > first
$ _autoenv_authorize first
$ cat $AUTOENV_ENV_FILENAME
:/tmp/cramtests-*/_autoenv_utils.t/first:271ac93c44ac198d92e706c6d6f1d84aefcfa337:1 (glob)
And a second one.
$ echo second > second
$ _autoenv_authorize second
$ cat $AUTOENV_ENV_FILENAME
:/tmp/cramtests-*/_autoenv_utils.t/first:271ac93c44ac198d92e706c6d6f1d84aefcfa337:1 (glob)
:/tmp/cramtests-*/_autoenv_utils.t/second:7bee8f3b184e1e141ff76efe369c3b8bfc50e64c:1 (glob)
And a third.
$ echo third > third
$ _autoenv_authorize third
$ cat $AUTOENV_ENV_FILENAME
:/tmp/cramtests-*/_autoenv_utils.t/first:271ac93c44ac198d92e706c6d6f1d84aefcfa337:1 (glob)
:/tmp/cramtests-*/_autoenv_utils.t/second:7bee8f3b184e1e141ff76efe369c3b8bfc50e64c:1 (glob)
:/tmp/cramtests-*/_autoenv_utils.t/third:ad180453bf8a374a15df3e90a78c180230146a7c:1 (glob)
Re-add the second one, with the same hash.
$ _autoenv_authorize second
$ cat $AUTOENV_ENV_FILENAME
:/tmp/cramtests-*/_autoenv_utils.t/first:271ac93c44ac198d92e706c6d6f1d84aefcfa337:1 (glob)
:/tmp/cramtests-*/_autoenv_utils.t/third:ad180453bf8a374a15df3e90a78c180230146a7c:1 (glob)
:/tmp/cramtests-*/_autoenv_utils.t/second:7bee8f3b184e1e141ff76efe369c3b8bfc50e64c:1 (glob)
Re-add the first one, with a new hash.
$ echo one more line >> first
$ _autoenv_authorize first
$ cat $AUTOENV_ENV_FILENAME
:/tmp/cramtests-*/_autoenv_utils.t/third:ad180453bf8a374a15df3e90a78c180230146a7c:1 (glob)
:/tmp/cramtests-*/_autoenv_utils.t/second:7bee8f3b184e1e141ff76efe369c3b8bfc50e64c:1 (glob)
:/tmp/cramtests-*/_autoenv_utils.t/first:65eb010197b73ddc109b7210080f97a87f53451e:1 (glob)
}}}
Explicit calls to _autoenv_get_file_mtime to test alternative implementation
of _autoenv_get_file_mtime (via ZDOTDIR.invalid-module_path/).
$ _autoenv_get_file_mtime non-existing
0
$ touch -t 201401010101 file
$ _autoenv_get_file_mtime file
1388538060
$ mkdir dir
$ touch -t 201401010102 dir
$ _autoenv_get_file_mtime dir
1388538120

View File

@ -1,89 +1,87 @@
Ensure we have our mocked out ENV_AUTHORIZATION_FILE
$ [[ $ENV_AUTHORIZATION_FILE[0,4] == '/tmp' ]] || return 1
$ source $TESTDIR/setup.sh || return 1
Lets set a simple .env action
$ echo 'echo blah' >> .env
$ echo 'echo ENTERED' > .env
Manually create auth file
$ echo "$PWD/.env:$(echo echo blah | shasum)" > $ENV_AUTHORIZATION_FILE
$ test_autoenv_add_to_env $PWD/.env
$ cd .
blah
ENTERED
Now try to make it accept it
$ rm $ENV_AUTHORIZATION_FILE
$ _dotenv_read_answer() { answer='y' }
$ _autoenv_stack_entered=()
$ rm $AUTOENV_ENV_FILENAME
$ _autoenv_ask_for_yes() { echo "yes" }
$ cd .
Attempting to load unauthorized env: /tmp/cramtests-??????/autoenv.t/.env (glob)
Attempting to load unauthorized env file!
-* /tmp/cramtests-*/autoenv.t/.env (glob)
**********************************************
echo blah
echo ENTERED
**********************************************
Would you like to authorize it? (y/n)
blah
Would you like to authorize it? (type 'yes') yes
ENTERED
The last "ENTERED" is because it executed the command.
Now lets see that it actually checks the shasum value.
The last "blah" is because it executed the command
Now lets see that it actually checks the shasum value
$ _autoenv_stack_entered=()
$ cd .
blah
$ rm $ENV_AUTHORIZATION_FILE
$ echo "$PWD/.env:$(echo mischief | shasum)" > $ENV_AUTHORIZATION_FILE
ENTERED
$ _autoenv_stack_entered=()
$ rm $AUTOENV_ENV_FILENAME
$ test_autoenv_add_to_env $PWD/.env mischief
$ cd .
Attempting to load unauthorized env: /tmp/cramtests-??????/autoenv.t/.env (glob)
Attempting to load unauthorized env file!
-* /tmp/cramtests-*/autoenv.t/.env (glob)
**********************************************
echo blah
echo ENTERED
**********************************************
Would you like to authorize it? (y/n)
blah
Would you like to authorize it? (type 'yes') yes
ENTERED
Now, will it take no for an answer?
$ rm $ENV_AUTHORIZATION_FILE
$ _dotenv_read_answer() { answer='n' }
$ _autoenv_stack_entered=()
$ rm $AUTOENV_ENV_FILENAME
$ _autoenv_ask_for_yes() { echo "no"; return 1 }
$ cd .
Attempting to load unauthorized env: /tmp/cramtests-??????/autoenv.t/.env (glob)
Attempting to load unauthorized env file!
-* /tmp/cramtests-*/autoenv.t/.env (glob)
**********************************************
echo blah
echo ENTERED
**********************************************
Would you like to authorize it? (y/n)
Would you like to authorize it? (type 'yes') no
Lets also try one more time to ensure it didnt add it
Lets also try one more time to ensure it didn't add it.
$ cd .
Attempting to load unauthorized env: /tmp/cramtests-??????/autoenv.t/.env (glob)
Attempting to load unauthorized env file!
-* /tmp/cramtests-*/autoenv.t/.env (glob)
**********************************************
echo blah
echo ENTERED
**********************************************
Would you like to authorize it? (y/n)
Would you like to authorize it? (type 'yes') no

40
tests/cwd.t Normal file
View File

@ -0,0 +1,40 @@
Test $PWD, $autoenv_event, $autoenv_from_dir and $autoenv_to_dir.
$ source $TESTDIR/setup.sh || return 1
Setup env actions / output.
$ AUTOENV_LOOK_UPWARDS=1
$ mkdir -p sub/sub2
$ cd sub
$ echo 'echo ENTERED: PWD:${PWD:t} pwd:${${"$(pwd)"}:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t} event:${autoenv_event}' > .env
$ echo 'echo LEFT: PWD:${PWD:t} pwd:${${"$(pwd)"}:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t} event:${autoenv_event}' > .env.leave
Manually create auth files.
$ test_autoenv_auth_env_files
The actual tests.
$ cd .
ENTERED: PWD:sub pwd:sub from:sub to:sub event:enter
$ cd ..
LEFT: PWD:cwd.t pwd:cwd.t from:sub to:cwd.t event:leave
$ cd sub/sub2
ENTERED: PWD:sub2 pwd:sub2 from:cwd.t to:sub2 event:enter
Check that symlinked dirs get handled correctly.
$ cd ../..
LEFT: PWD:cwd.t pwd:cwd.t from:sub2 to:cwd.t event:leave
$ ln -s sub sub_linked
$ cd sub_linked
ENTERED: PWD:sub_linked pwd:sub_linked from:cwd.t to:sub_linked event:enter
$ cd sub2
$ cd ../..
LEFT: PWD:cwd.t pwd:cwd.t from:sub2 to:cwd.t event:leave
$ cd sub_linked/sub2
ENTERED: PWD:sub2 pwd:sub2 from:cwd.t to:sub2 event:enter

115
tests/leave.t Normal file
View File

@ -0,0 +1,115 @@
$ source $TESTDIR/setup.sh || return 1
Lets set a simple .env action
$ mkdir sub
$ cd sub
$ echo 'echo ENTERED' > .env
$ echo 'echo LEFT' > .env.leave
Change to the directory.
$ _autoenv_ask_for_yes() { echo "yes"; return 0 }
$ cd .
Attempting to load unauthorized env file!
-* /tmp/cramtests-*/leave.t/sub/.env (glob)
**********************************************
echo ENTERED
**********************************************
Would you like to authorize it? (type 'yes') yes
ENTERED
Leave the directory and answer "no".
$ _autoenv_ask_for_yes() { echo "no"; return 1 }
$ cd ..
Attempting to load unauthorized env file!
-* /tmp/cramtests-*/leave.t/sub/.env.leave (glob)
**********************************************
echo LEFT
**********************************************
Would you like to authorize it? (type 'yes') no
$ cd sub
ENTERED
$ _autoenv_ask_for_yes() { echo "yes"; return 0 }
$ cd ..
Attempting to load unauthorized env file!
-* /tmp/cramtests-*/leave.t/sub/.env.leave (glob)
**********************************************
echo LEFT
**********************************************
Would you like to authorize it? (type 'yes') yes
LEFT
Now check with subdirs, looking upwards.
$ AUTOENV_LOOK_UPWARDS=1
$ mkdir sub/child
$ cd sub/child
ENTERED
$ cd .
$ cd ..
$ cd ..
LEFT
Now check with subdirs, not looking at parent dirs.
$ AUTOENV_LOOK_UPWARDS=0
$ cd sub/child
$ cd ..
ENTERED
$ cd child
$ cd ../..
LEFT
Test that .env is sourced only once with AUTOENV_HANDLE_LEAVE=0.
$ unset _autoenv_stack_entered
$ AUTOENV_HANDLE_LEAVE=0
$ cd sub
ENTERED
$ cd ..
$ cd sub
Test that "leave" is not triggered when entering an outside dir via symlink.
$ AUTOENV_HANDLE_LEAVE=1
$ cd ..
LEFT
$ mkdir outside
$ cd outside
$ echo 'echo ENTERED outside: PWD:${PWD:t} pwd:${${"$(pwd)"}:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t} event:${autoenv_event}' > .env
$ echo 'echo LEFT outside: PWD:${PWD:t} pwd:${${"$(pwd)"}:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t} event:${autoenv_event}' > .env.leave
$ test_autoenv_auth_env_files
$ cd ..
$ ln -s ../outside sub/symlink
$ cd sub
ENTERED
$ cd symlink
ENTERED outside: PWD:symlink pwd:symlink from:sub to:symlink event:enter
$ cd ../..
LEFT
LEFT outside: PWD:leave.t pwd:leave.t from:symlink to:leave.t event:leave
$ cd sub/symlink
ENTERED outside: PWD:symlink pwd:symlink from:leave.t to:symlink event:enter

167
tests/recurse-upwards.t Normal file
View File

@ -0,0 +1,167 @@
Test recursing into parent .env files.
$ source $TESTDIR/setup.sh || return 1
Setup env actions / output.
$ AUTOENV_LOOK_UPWARDS=1
Create env files in root dir.
$ echo 'echo ENTERED_root: PWD:${PWD:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t}' > .env
$ echo 'echo LEFT_root: PWD:${PWD:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t}' > .env.leave
$ test_autoenv_auth_env_files
Create env files in sub dir.
$ mkdir -p sub/sub2
$ cd sub
ENTERED_root: PWD:sub from:recurse-upwards.t to:sub
$ echo 'echo ENTERED_sub: PWD:${PWD:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t}' > .env
$ echo 'echo LEFT_sub: PWD:${PWD:t} from:${autoenv_from_dir:t} to:${autoenv_to_dir:t}' > .env.leave
$ test_autoenv_auth_env_files
The actual tests.
$ cd .
ENTERED_sub: PWD:sub from:sub to:sub
$ cd ..
LEFT_sub: PWD:recurse-upwards.t from:sub to:recurse-upwards.t
$ cd sub/sub2
ENTERED_sub: PWD:sub2 from:recurse-upwards.t to:sub2
$ cd ..
Changing the .env file should re-source it.
$ echo 'echo ENTER2' >> .env
Set timestamp of auth file into the past, so it gets seen as new below.
$ touch -t 201401010101 .env
$ test_autoenv_auth_env_files
$ cd .
ENTERED_sub: PWD:sub from:sub to:sub
ENTER2
Add sub/sub2/.env file, with a call to autoenv_source_parent.
$ echo "echo autoenv_source_parent_from_sub2:\nautoenv_source_parent\necho done_sub2\n" > sub2/.env
$ test_autoenv_add_to_env sub2/.env
$ cd sub2
autoenv_source_parent_from_sub2:
ENTERED_sub: PWD:sub2 from:sub to:sub2
ENTER2
done_sub2
Move sub/.env away, now the root .env file should get sourced.
$ mv ../.env ../.env.out
$ touch -t 201401010102 .env
$ cd .
autoenv_source_parent_from_sub2:
ENTERED_root: PWD:sub2 from:sub2 to:sub2
done_sub2
$ mv ../.env.out ../.env
Prepend call to autoenv_source_parent to sub/.env file.
$ cd ..
$ sed -i -e "1s/^/echo autoenv_source_parent_from_sub:\nautoenv_source_parent\n/" .env
$ echo "echo done_sub" >> .env
$ touch -t 201401010103 .env
$ test_autoenv_auth_env_files
$ cd .
autoenv_source_parent_from_sub:
ENTERED_root: PWD:sub from:sub to:sub
ENTERED_sub: PWD:sub from:sub to:sub
ENTER2
done_sub
Add sub/sub2/.env file.
$ echo -e "echo autoenv_source_parent_from_sub2:\nautoenv_source_parent\necho done_sub2\n" >| sub2/.env
$ test_autoenv_add_to_env sub2/.env
$ cd sub2
autoenv_source_parent_from_sub2:
autoenv_source_parent_from_sub:
ENTERED_root: PWD:sub2 from:sub to:sub2
ENTERED_sub: PWD:sub2 from:sub to:sub2
ENTER2
done_sub
done_sub2
Go to root.
This should not trigger the enter event, because it was handled via
autoenv_source_parent already.
$ cd ../..
LEFT_sub: PWD:recurse-upwards.t from:sub2 to:recurse-upwards.t
Changing the root .env should trigger re-authentication via autoenv_source_parent.
First, let's answer "no".
$ echo "echo NEW" >| .env
$ _autoenv_ask_for_yes() { echo "no"; return 1 }
$ cd sub
autoenv_source_parent_from_sub:
Attempting to load unauthorized env file!
-* /tmp/cramtests-*/recurse-upwards.t/.env (glob)
**********************************************
echo NEW
**********************************************
Would you like to authorize it? (type 'yes') no
ENTERED_sub: PWD:sub from:recurse-upwards.t to:sub
ENTER2
done_sub
Now with "yes".
This currently does not trigger re-execution of the .env file.
$ _autoenv_ask_for_yes() { echo "yes"; return 0 }
$ cd .
Touching the .env file will now source the parent env file.
$ touch -t 201401010104 .env
$ cd .
autoenv_source_parent_from_sub:
Attempting to load unauthorized env file!
-* /tmp/cramtests-*/recurse-upwards.t/.env (glob)
**********************************************
echo NEW
**********************************************
Would you like to authorize it? (type 'yes') yes
NEW
ENTERED_sub: PWD:sub from:sub to:sub
ENTER2
done_sub
$ cd ..
LEFT_sub: PWD:recurse-upwards.t from:sub to:recurse-upwards.t
$ mkdir sub/sub2/sub3
$ cd sub/sub2/sub3
autoenv_source_parent_from_sub2:
autoenv_source_parent_from_sub:
NEW
ENTERED_sub: PWD:sub3 from:recurse-upwards.t to:sub3
ENTER2
done_sub
done_sub2

29
tests/setup.sh Normal file
View File

@ -0,0 +1,29 @@
# Setup for tests.
#
# It returns 1 in case of errors, and no tests should be run then!
#
# Ensure we have our mocked out AUTOENV_ENV_FILENAME
# (via .zshenv).
# Treat unset variables as errors.
# Not handled in varstash yet.
# setopt nounset
if [[ $AUTOENV_ENV_FILENAME[0,4] != '/tmp' ]]; then
echo "AUTOENV_ENV_FILENAME is not in /tmp. Aborting."
return 1
fi
# Reset any authentication.
echo -n >| $AUTOENV_ENV_FILENAME
# Add file $1 (with optional hash $2) to authentication file.
test_autoenv_add_to_env() {
_autoenv_hash_pair $1 ${2:-} >>| $AUTOENV_ENV_FILENAME
}
# Add enter and leave env files to authentication file.
test_autoenv_auth_env_files() {
test_autoenv_add_to_env $PWD/$AUTOENV_FILE_ENTER
test_autoenv_add_to_env $PWD/$AUTOENV_FILE_LEAVE
}

93
tests/varstash.t Normal file
View File

@ -0,0 +1,93 @@
Test varstash integration.
$ source $TESTDIR/setup.sh || return 1
Setup test environment.
$ mkdir sub
$ cd sub
$ echo 'echo ENTER; autostash FOO=changed' > $AUTOENV_FILE_ENTER
$ echo 'echo LEAVE; autounstash' > $AUTOENV_FILE_LEAVE
Manually create auth file
$ test_autoenv_auth_env_files
Set environment variable.
$ FOO=orig
Activating the env stashes it and applies a new value.
$ cd .
ENTER
$ echo $FOO
changed
Leaving the directory unstashes it.
$ cd ..
LEAVE
$ echo $FOO
orig
Test autounstashing when leaving a directory. {{{
Setup:
$ unset VAR
$ cd sub
ENTER
$ echo 'echo ENTER; autostash VAR=changed' >| $AUTOENV_FILE_ENTER
$ echo 'echo LEAVE; echo "no explicit call to autounstash"' >| $AUTOENV_FILE_LEAVE
$ test_autoenv_auth_env_files
$VAR is unset:
$ echo VAR_set:$+VAR
VAR_set:0
Trigger the autostashing in the enter file.
$ cd ..
LEAVE
no explicit call to autounstash
$ cd sub
ENTER
$ echo $VAR
changed
Now leave again.
$ cd ..
LEAVE
no explicit call to autounstash
$ echo VAR_set:$+VAR
VAR_set:0
Remove the leave file, auto-unstashing should still happen.
$ rm sub/$AUTOENV_FILE_LEAVE
$ cd sub
ENTER
$ echo $VAR
changed
$ cd ..
$ echo VAR_set:$+VAR
VAR_set:0
And once again where a value gets restored.
$ VAR=orig_2
$ echo $VAR
orig_2
$ cd sub
ENTER
$ echo $VAR
changed
$ cd ..
$ echo $VAR
orig_2
}}}