Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 114 additions & 2 deletions bin/pyenv-virtualenv-init
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@
set -e
[ -n "$PYENV_DEBUG" ] && set -x

# Detect stat format for mtime: GNU uses -c %Y, BSD uses -f %m
# -L follows symlinks: a symlinked .python-version reflects target changes
if stat -L -c %Y / >/dev/null 2>&1; then
_stat_fmt="-L -c %Y"
else
_stat_fmt="-L -f %m"
fi

# Check for version-name hooks at init time. Hooks can alter version
# resolution in ways the mtime cache cannot track. If present, the hook
# falls back to upstream behavior (no caching). Restart shell after
# installing or removing pyenv plugins.
_has_version_hooks=""
if [ -n "$(pyenv hooks version-name 2>/dev/null)" ]; then
_has_version_hooks=1
fi

resolve_link() {
$(type -p greadlink readlink | head -1) "$1"
}
Expand Down Expand Up @@ -103,7 +120,8 @@ esac

case "$shell" in
fish )
cat <<EOS
if [ -n "$_has_version_hooks" ]; then
cat <<EOS
function _pyenv_virtualenv_hook --on-event fish_prompt;
set -l ret \$status
if [ -n "\$VIRTUAL_ENV" ]
Expand All @@ -114,6 +132,53 @@ function _pyenv_virtualenv_hook --on-event fish_prompt;
return \$ret
end
EOS
else
cat <<EOS
function _pyenv_virtualenv_hook --on-event fish_prompt;
set -l ret \$status
if test "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\
-a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV"
if test -n "\$PYENV_VERSION"
return \$ret
end
if test "\$PWD" = "\$_PYENV_VH_PWD" \\
-a "(stat ${_stat_fmt} \$_PYENV_VH_PATHS 2>/dev/null)" = "\$_PYENV_VH_MTIMES"
return \$ret
end
end
if [ -n "\$VIRTUAL_ENV" ]
pyenv activate --quiet; or pyenv deactivate --quiet; or true
else
pyenv activate --quiet; or true
end
set -g _PYENV_VH_PWD "\$PWD"
set -g _PYENV_VH_VERSION "\$PYENV_VERSION"
set -g _PYENV_VH_VENV "\$VIRTUAL_ENV"
set -l d "\$PWD"
set -l _pvh_found_local 0
set -g _PYENV_VH_PATHS
while true
if test -f "\$d/.python-version"; or test -L "\$d/.python-version"
set -g _PYENV_VH_PATHS \$_PYENV_VH_PATHS "\$d/.python-version"
if test -f "\$d/.python-version"
set _pvh_found_local 1
break
end
else
set -g _PYENV_VH_PATHS \$_PYENV_VH_PATHS "\$d"
end
test "\$d" = "/"; and break
set d (string replace -r '/[^/]*\$' '' -- "\$d")
test -z "\$d"; and set d "/"
end
if test "\$_pvh_found_local" = "0"
set -g _PYENV_VH_PATHS \$_PYENV_VH_PATHS "\$PYENV_ROOT/version"
end
set -g _PYENV_VH_MTIMES (stat ${_stat_fmt} \$_PYENV_VH_PATHS 2>/dev/null)
return \$ret
end
EOS
fi
;;
ksh )
cat <<EOS
Expand All @@ -128,7 +193,8 @@ EOS
esac

if [[ "$shell" != "fish" ]]; then
cat <<EOS
if [ -n "$_has_version_hooks" ]; then
cat <<EOS
local ret=\$?
if [ -n "\${VIRTUAL_ENV-}" ]; then
eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
Expand All @@ -138,6 +204,52 @@ if [[ "$shell" != "fish" ]]; then
return \$ret
};
EOS
else
cat <<EOS
local ret=\$?
# Cache: env vars checked once, path list and stat rebuilt on miss only
if [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then
if [ -n "\${PYENV_VERSION-}" ]; then
return \$ret
fi
if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\
&& [ "\$(stat ${_stat_fmt} "\${_PYENV_VH_PATHS[@]}" 2>/dev/null)" = "\${_PYENV_VH_MTIMES-}" ]; then
return \$ret
fi
fi
if [ -n "\${VIRTUAL_ENV-}" ]; then
eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
else
eval "\$(pyenv sh-activate --quiet || true)" || true
fi
_PYENV_VH_PWD="\${PWD}"
_PYENV_VH_VERSION="\${PYENV_VERSION-}"
_PYENV_VH_VENV="\${VIRTUAL_ENV-}"
local _pvh_d="\${PWD}" _pvh_found_local=0
_PYENV_VH_PATHS=()
while :; do
if [ -f "\${_pvh_d}/.python-version" ] || [ -L "\${_pvh_d}/.python-version" ]; then
_PYENV_VH_PATHS+=("\${_pvh_d}/.python-version")
if [ -f "\${_pvh_d}/.python-version" ]; then
_pvh_found_local=1
break
fi
else
_PYENV_VH_PATHS+=("\${_pvh_d}")
fi
[ "\${_pvh_d}" = "/" ] && break
_pvh_d="\${_pvh_d%/*}"
[ -z "\${_pvh_d}" ] && _pvh_d="/"
done
if [ "\${_pvh_found_local}" = "0" ]; then
_PYENV_VH_PATHS+=("\${PYENV_ROOT}/version")
fi
_PYENV_VH_MTIMES="\$(stat ${_stat_fmt} "\${_PYENV_VH_PATHS[@]}" 2>/dev/null)"
return \$ret
};
EOS
fi

case "$shell" in
bash )
Expand Down
102 changes: 102 additions & 0 deletions test/init.bats
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,45 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}";
export PYENV_VIRTUALENV_INIT=1;
_pyenv_virtualenv_hook() {
local ret=\$?
# Cache: env vars checked once, path list and stat rebuilt on miss only
if [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then
if [ -n "\${PYENV_VERSION-}" ]; then
return \$ret
fi
if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\
&& [ "\$(stat ${_stat_fmt} "\${_PYENV_VH_PATHS[@]}" 2>/dev/null)" = "\${_PYENV_VH_MTIMES-}" ]; then
return \$ret
fi
fi
if [ -n "\${VIRTUAL_ENV-}" ]; then
eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
else
eval "\$(pyenv sh-activate --quiet || true)" || true
fi
_PYENV_VH_PWD="\${PWD}"
_PYENV_VH_VERSION="\${PYENV_VERSION-}"
_PYENV_VH_VENV="\${VIRTUAL_ENV-}"
local _pvh_d="\${PWD}" _pvh_found_local=0
_PYENV_VH_PATHS=()
while :; do
if [ -f "\${_pvh_d}/.python-version" ] || [ -L "\${_pvh_d}/.python-version" ]; then
_PYENV_VH_PATHS+=("\${_pvh_d}/.python-version")
if [ -f "\${_pvh_d}/.python-version" ]; then
_pvh_found_local=1
break
fi
else
_PYENV_VH_PATHS+=("\${_pvh_d}")
fi
[ "\${_pvh_d}" = "/" ] && break
_pvh_d="\${_pvh_d%/*}"
[ -z "\${_pvh_d}" ] && _pvh_d="/"
done
if [ "\${_pvh_found_local}" = "0" ]; then
_PYENV_VH_PATHS+=("\${PYENV_ROOT}/version")
fi
_PYENV_VH_MTIMES="\$(stat ${_stat_fmt} "\${_PYENV_VH_PATHS[@]}" 2>/dev/null)"
return \$ret
};
if ! [[ "\${PROMPT_COMMAND-}" =~ _pyenv_virtualenv_hook ]]; then
Expand All @@ -78,11 +112,45 @@ set -gx PATH '${TMP}/pyenv/plugins/pyenv-virtualenv/shims' \$PATH;
set -gx PYENV_VIRTUALENV_INIT 1;
function _pyenv_virtualenv_hook --on-event fish_prompt;
set -l ret \$status
if test "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\
-a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV"
if test -n "\$PYENV_VERSION"
return \$ret
end
if test "\$PWD" = "\$_PYENV_VH_PWD" \\
-a "(stat ${_stat_fmt} \$_PYENV_VH_PATHS 2>/dev/null)" = "\$_PYENV_VH_MTIMES"
return \$ret
end
end
if [ -n "\$VIRTUAL_ENV" ]
pyenv activate --quiet; or pyenv deactivate --quiet; or true
else
pyenv activate --quiet; or true
end
set -g _PYENV_VH_PWD "\$PWD"
set -g _PYENV_VH_VERSION "\$PYENV_VERSION"
set -g _PYENV_VH_VENV "\$VIRTUAL_ENV"
set -l d "\$PWD"
set -l _pvh_found_local 0
set -g _PYENV_VH_PATHS
while true
if test -f "\$d/.python-version"; or test -L "\$d/.python-version"
set -g _PYENV_VH_PATHS \$_PYENV_VH_PATHS "\$d/.python-version"
if test -f "\$d/.python-version"
set _pvh_found_local 1
break
end
else
set -g _PYENV_VH_PATHS \$_PYENV_VH_PATHS "\$d"
end
test "\$d" = "/"; and break
set d (string replace -r '/[^/]*\$' '' -- "\$d")
test -z "\$d"; and set d "/"
end
if test "\$_pvh_found_local" = "0"
set -g _PYENV_VH_PATHS \$_PYENV_VH_PATHS "\$PYENV_ROOT/version"
end
set -g _PYENV_VH_MTIMES (stat ${_stat_fmt} \$_PYENV_VH_PATHS 2>/dev/null)
return \$ret
end
EOS
Expand All @@ -97,11 +165,45 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}";
export PYENV_VIRTUALENV_INIT=1;
_pyenv_virtualenv_hook() {
local ret=\$?
# Cache: env vars checked once, path list and stat rebuilt on miss only
if [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then
if [ -n "\${PYENV_VERSION-}" ]; then
return \$ret
fi
if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\
&& [ "\$(stat ${_stat_fmt} "\${_PYENV_VH_PATHS[@]}" 2>/dev/null)" = "\${_PYENV_VH_MTIMES-}" ]; then
return \$ret
fi
fi
if [ -n "\${VIRTUAL_ENV-}" ]; then
eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
else
eval "\$(pyenv sh-activate --quiet || true)" || true
fi
_PYENV_VH_PWD="\${PWD}"
_PYENV_VH_VERSION="\${PYENV_VERSION-}"
_PYENV_VH_VENV="\${VIRTUAL_ENV-}"
local _pvh_d="\${PWD}" _pvh_found_local=0
_PYENV_VH_PATHS=()
while :; do
if [ -f "\${_pvh_d}/.python-version" ] || [ -L "\${_pvh_d}/.python-version" ]; then
_PYENV_VH_PATHS+=("\${_pvh_d}/.python-version")
if [ -f "\${_pvh_d}/.python-version" ]; then
_pvh_found_local=1
break
fi
else
_PYENV_VH_PATHS+=("\${_pvh_d}")
fi
[ "\${_pvh_d}" = "/" ] && break
_pvh_d="\${_pvh_d%/*}"
[ -z "\${_pvh_d}" ] && _pvh_d="/"
done
if [ "\${_pvh_found_local}" = "0" ]; then
_PYENV_VH_PATHS+=("\${PYENV_ROOT}/version")
fi
_PYENV_VH_MTIMES="\$(stat ${_stat_fmt} "\${_PYENV_VH_PATHS[@]}" 2>/dev/null)"
return \$ret
};
typeset -g -a precmd_functions
Expand Down
8 changes: 8 additions & 0 deletions test/test_helper.bash
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
export TMP="$BATS_TEST_DIRNAME/tmp"

# Detect stat format for mtime: GNU uses -c %Y, BSD uses -f %m
# Must match the detection in bin/pyenv-virtualenv-init
if stat -L -c %Y / >/dev/null 2>&1; then
_stat_fmt="-L -c %Y"
else
_stat_fmt="-L -f %m"
fi
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'

PATH=/usr/bin:/usr/sbin:/bin:/sbin
Expand Down
Loading