Skip to content

Commit 7ca917c

Browse files
committed
Revise hook cache: mtime-based directory walk
Replace content reads ($(< .python-version), which fork a subshell in bash) with test -nt against a per-session marker file (shell builtin, zero forks). Walk the full directory tree from $PWD to / checking .python-version at each level, matching pyenv's "closest wins" resolution order. Check directory mtimes to detect file creation/deletion. Check $PYENV_ROOT/version only when no local .python-version exists anywhere in the tree. Skip all disk checks when $PYENV_VERSION is set (shell priority).
1 parent bb9a634 commit 7ca917c

2 files changed

Lines changed: 125 additions & 70 deletions

File tree

bin/pyenv-virtualenv-init

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -106,20 +106,31 @@ fish )
106106
cat <<EOS
107107
function _pyenv_virtualenv_hook --on-event fish_prompt;
108108
set -l ret \$status
109-
set -l pvh_local ""
110-
if test -f "\$PWD/.python-version"
111-
read -z pvh_local < "\$PWD/.python-version" 2>/dev/null; or true
112-
end
113-
set -l pvh_global ""
114-
if test -f "\$PYENV_ROOT/version"
115-
read -z pvh_global < "\$PYENV_ROOT/version" 2>/dev/null; or true
116-
end
117109
if test "\$PWD" = "\$_PYENV_VH_PWD" \\
118110
-a "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\
119-
-a "\$pvh_local" = "\$_PYENV_VH_LOCAL" \\
120-
-a "\$pvh_global" = "\$_PYENV_VH_GLOBAL" \\
121-
-a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV"
122-
return \$ret
111+
-a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV" \\
112+
-a -f "\$_PYENV_VH_MARKER"
113+
if test -n "\$PYENV_VERSION"
114+
return \$ret
115+
end
116+
set -l d "\$PWD"
117+
set -l stale 0
118+
set -l found 0
119+
while true
120+
if test -f "\$d/.python-version"
121+
set found 1
122+
test "\$d/.python-version" -nt "\$_PYENV_VH_MARKER"; and set stale 1
123+
break
124+
end
125+
test "\$d" -nt "\$_PYENV_VH_MARKER"; and begin; set stale 1; break; end
126+
test "\$d" = "/"; and break
127+
set d (string replace -r '/[^/]*\$' '' -- "\$d")
128+
test -z "\$d"; and set d "/"
129+
end
130+
if test \$stale = 0 -a \$found = 0
131+
test -f "\$PYENV_ROOT/version"; and test "\$PYENV_ROOT/version" -nt "\$_PYENV_VH_MARKER"; and set stale 1
132+
end
133+
test \$stale = 0; and return \$ret
123134
end
124135
if [ -n "\$VIRTUAL_ENV" ]
125136
pyenv activate --quiet; or pyenv deactivate --quiet; or true
@@ -128,9 +139,9 @@ function _pyenv_virtualenv_hook --on-event fish_prompt;
128139
end
129140
set -g _PYENV_VH_PWD "\$PWD"
130141
set -g _PYENV_VH_VERSION "\$PYENV_VERSION"
131-
set -g _PYENV_VH_LOCAL "\$pvh_local"
132-
set -g _PYENV_VH_GLOBAL "\$pvh_global"
133142
set -g _PYENV_VH_VENV "\$VIRTUAL_ENV"
143+
set -g _PYENV_VH_MARKER "\$PYENV_ROOT/.pyenv-vh-marker-\$fish_pid"
144+
command touch "\$_PYENV_VH_MARKER"
134145
return \$ret
135146
end
136147
EOS
@@ -150,20 +161,31 @@ esac
150161
if [[ "$shell" != "fish" ]]; then
151162
cat <<EOS
152163
local ret=\$?
153-
local pvh_local=""
154-
if [ -f "\${PWD}/.python-version" ]; then
155-
pvh_local=\$(< "\${PWD}/.python-version") 2>/dev/null || true
156-
fi
157-
local pvh_global=""
158-
if [ -f "\${PYENV_ROOT}/version" ]; then
159-
pvh_global=\$(< "\${PYENV_ROOT}/version") 2>/dev/null || true
160-
fi
161164
if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\
162165
&& [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\
163-
&& [ "\${pvh_local}" = "\${_PYENV_VH_LOCAL-}" ] \\
164-
&& [ "\${pvh_global}" = "\${_PYENV_VH_GLOBAL-}" ] \\
165-
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then
166-
return \$ret
166+
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ] \\
167+
&& [ -f "\${_PYENV_VH_MARKER-}" ]; then
168+
if [ -n "\${PYENV_VERSION-}" ]; then
169+
return \$ret
170+
fi
171+
local _pvh_d="\${PWD}" _pvh_stale=0 _pvh_found=0
172+
while :; do
173+
if [ -f "\${_pvh_d}/.python-version" ]; then
174+
_pvh_found=1
175+
[ "\${_pvh_d}/.python-version" -nt "\${_PYENV_VH_MARKER}" ] && _pvh_stale=1
176+
break
177+
fi
178+
[ "\${_pvh_d}" -nt "\${_PYENV_VH_MARKER}" ] && { _pvh_stale=1; break; }
179+
[ "\${_pvh_d}" = "/" ] && break
180+
_pvh_d="\${_pvh_d%/*}"
181+
[ -z "\${_pvh_d}" ] && _pvh_d="/"
182+
done
183+
if [ "\${_pvh_stale}" = 0 ] && [ "\${_pvh_found}" = 0 ]; then
184+
[ -f "\${PYENV_ROOT}/version" ] \\
185+
&& [ "\${PYENV_ROOT}/version" -nt "\${_PYENV_VH_MARKER}" ] \\
186+
&& _pvh_stale=1
187+
fi
188+
[ "\${_pvh_stale}" = 0 ] && return \$ret
167189
fi
168190
if [ -n "\${VIRTUAL_ENV-}" ]; then
169191
eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
@@ -172,9 +194,9 @@ if [[ "$shell" != "fish" ]]; then
172194
fi
173195
_PYENV_VH_PWD="\${PWD}"
174196
_PYENV_VH_VERSION="\${PYENV_VERSION-}"
175-
_PYENV_VH_LOCAL="\${pvh_local}"
176-
_PYENV_VH_GLOBAL="\${pvh_global}"
177197
_PYENV_VH_VENV="\${VIRTUAL_ENV-}"
198+
_PYENV_VH_MARKER="\${PYENV_ROOT}/.pyenv-vh-marker-\$\$"
199+
: > "\${_PYENV_VH_MARKER}"
178200
return \$ret
179201
};
180202
EOS

test/init.bats

Lines changed: 75 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,31 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}";
5454
export PYENV_VIRTUALENV_INIT=1;
5555
_pyenv_virtualenv_hook() {
5656
local ret=\$?
57-
local pvh_local=""
58-
if [ -f "\${PWD}/.python-version" ]; then
59-
pvh_local=\$(< "\${PWD}/.python-version") 2>/dev/null || true
60-
fi
61-
local pvh_global=""
62-
if [ -f "\${PYENV_ROOT}/version" ]; then
63-
pvh_global=\$(< "\${PYENV_ROOT}/version") 2>/dev/null || true
64-
fi
6557
if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\
6658
&& [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\
67-
&& [ "\${pvh_local}" = "\${_PYENV_VH_LOCAL-}" ] \\
68-
&& [ "\${pvh_global}" = "\${_PYENV_VH_GLOBAL-}" ] \\
69-
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then
70-
return \$ret
59+
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ] \\
60+
&& [ -f "\${_PYENV_VH_MARKER-}" ]; then
61+
if [ -n "\${PYENV_VERSION-}" ]; then
62+
return \$ret
63+
fi
64+
local _pvh_d="\${PWD}" _pvh_stale=0 _pvh_found=0
65+
while :; do
66+
if [ -f "\${_pvh_d}/.python-version" ]; then
67+
_pvh_found=1
68+
[ "\${_pvh_d}/.python-version" -nt "\${_PYENV_VH_MARKER}" ] && _pvh_stale=1
69+
break
70+
fi
71+
[ "\${_pvh_d}" -nt "\${_PYENV_VH_MARKER}" ] && { _pvh_stale=1; break; }
72+
[ "\${_pvh_d}" = "/" ] && break
73+
_pvh_d="\${_pvh_d%/*}"
74+
[ -z "\${_pvh_d}" ] && _pvh_d="/"
75+
done
76+
if [ "\${_pvh_stale}" = 0 ] && [ "\${_pvh_found}" = 0 ]; then
77+
[ -f "\${PYENV_ROOT}/version" ] \\
78+
&& [ "\${PYENV_ROOT}/version" -nt "\${_PYENV_VH_MARKER}" ] \\
79+
&& _pvh_stale=1
80+
fi
81+
[ "\${_pvh_stale}" = 0 ] && return \$ret
7182
fi
7283
if [ -n "\${VIRTUAL_ENV-}" ]; then
7384
eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
@@ -76,9 +87,9 @@ _pyenv_virtualenv_hook() {
7687
fi
7788
_PYENV_VH_PWD="\${PWD}"
7889
_PYENV_VH_VERSION="\${PYENV_VERSION-}"
79-
_PYENV_VH_LOCAL="\${pvh_local}"
80-
_PYENV_VH_GLOBAL="\${pvh_global}"
8190
_PYENV_VH_VENV="\${VIRTUAL_ENV-}"
91+
_PYENV_VH_MARKER="\${PYENV_ROOT}/.pyenv-vh-marker-\$\$"
92+
: > "\${_PYENV_VH_MARKER}"
8293
return \$ret
8394
};
8495
if ! [[ "\${PROMPT_COMMAND-}" =~ _pyenv_virtualenv_hook ]]; then
@@ -98,20 +109,31 @@ set -gx PATH '${TMP}/pyenv/plugins/pyenv-virtualenv/shims' \$PATH;
98109
set -gx PYENV_VIRTUALENV_INIT 1;
99110
function _pyenv_virtualenv_hook --on-event fish_prompt;
100111
set -l ret \$status
101-
set -l pvh_local ""
102-
if test -f "\$PWD/.python-version"
103-
read -z pvh_local < "\$PWD/.python-version" 2>/dev/null; or true
104-
end
105-
set -l pvh_global ""
106-
if test -f "\$PYENV_ROOT/version"
107-
read -z pvh_global < "\$PYENV_ROOT/version" 2>/dev/null; or true
108-
end
109112
if test "\$PWD" = "\$_PYENV_VH_PWD" \\
110113
-a "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\
111-
-a "\$pvh_local" = "\$_PYENV_VH_LOCAL" \\
112-
-a "\$pvh_global" = "\$_PYENV_VH_GLOBAL" \\
113-
-a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV"
114-
return \$ret
114+
-a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV" \\
115+
-a -f "\$_PYENV_VH_MARKER"
116+
if test -n "\$PYENV_VERSION"
117+
return \$ret
118+
end
119+
set -l d "\$PWD"
120+
set -l stale 0
121+
set -l found 0
122+
while true
123+
if test -f "\$d/.python-version"
124+
set found 1
125+
test "\$d/.python-version" -nt "\$_PYENV_VH_MARKER"; and set stale 1
126+
break
127+
end
128+
test "\$d" -nt "\$_PYENV_VH_MARKER"; and begin; set stale 1; break; end
129+
test "\$d" = "/"; and break
130+
set d (string replace -r '/[^/]*\$' '' -- "\$d")
131+
test -z "\$d"; and set d "/"
132+
end
133+
if test \$stale = 0 -a \$found = 0
134+
test -f "\$PYENV_ROOT/version"; and test "\$PYENV_ROOT/version" -nt "\$_PYENV_VH_MARKER"; and set stale 1
135+
end
136+
test \$stale = 0; and return \$ret
115137
end
116138
if [ -n "\$VIRTUAL_ENV" ]
117139
pyenv activate --quiet; or pyenv deactivate --quiet; or true
@@ -120,9 +142,9 @@ function _pyenv_virtualenv_hook --on-event fish_prompt;
120142
end
121143
set -g _PYENV_VH_PWD "\$PWD"
122144
set -g _PYENV_VH_VERSION "\$PYENV_VERSION"
123-
set -g _PYENV_VH_LOCAL "\$pvh_local"
124-
set -g _PYENV_VH_GLOBAL "\$pvh_global"
125145
set -g _PYENV_VH_VENV "\$VIRTUAL_ENV"
146+
set -g _PYENV_VH_MARKER "\$PYENV_ROOT/.pyenv-vh-marker-\$fish_pid"
147+
command touch "\$_PYENV_VH_MARKER"
126148
return \$ret
127149
end
128150
EOS
@@ -137,20 +159,31 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}";
137159
export PYENV_VIRTUALENV_INIT=1;
138160
_pyenv_virtualenv_hook() {
139161
local ret=\$?
140-
local pvh_local=""
141-
if [ -f "\${PWD}/.python-version" ]; then
142-
pvh_local=\$(< "\${PWD}/.python-version") 2>/dev/null || true
143-
fi
144-
local pvh_global=""
145-
if [ -f "\${PYENV_ROOT}/version" ]; then
146-
pvh_global=\$(< "\${PYENV_ROOT}/version") 2>/dev/null || true
147-
fi
148162
if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\
149163
&& [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\
150-
&& [ "\${pvh_local}" = "\${_PYENV_VH_LOCAL-}" ] \\
151-
&& [ "\${pvh_global}" = "\${_PYENV_VH_GLOBAL-}" ] \\
152-
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then
153-
return \$ret
164+
&& [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ] \\
165+
&& [ -f "\${_PYENV_VH_MARKER-}" ]; then
166+
if [ -n "\${PYENV_VERSION-}" ]; then
167+
return \$ret
168+
fi
169+
local _pvh_d="\${PWD}" _pvh_stale=0 _pvh_found=0
170+
while :; do
171+
if [ -f "\${_pvh_d}/.python-version" ]; then
172+
_pvh_found=1
173+
[ "\${_pvh_d}/.python-version" -nt "\${_PYENV_VH_MARKER}" ] && _pvh_stale=1
174+
break
175+
fi
176+
[ "\${_pvh_d}" -nt "\${_PYENV_VH_MARKER}" ] && { _pvh_stale=1; break; }
177+
[ "\${_pvh_d}" = "/" ] && break
178+
_pvh_d="\${_pvh_d%/*}"
179+
[ -z "\${_pvh_d}" ] && _pvh_d="/"
180+
done
181+
if [ "\${_pvh_stale}" = 0 ] && [ "\${_pvh_found}" = 0 ]; then
182+
[ -f "\${PYENV_ROOT}/version" ] \\
183+
&& [ "\${PYENV_ROOT}/version" -nt "\${_PYENV_VH_MARKER}" ] \\
184+
&& _pvh_stale=1
185+
fi
186+
[ "\${_pvh_stale}" = 0 ] && return \$ret
154187
fi
155188
if [ -n "\${VIRTUAL_ENV-}" ]; then
156189
eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
@@ -159,9 +192,9 @@ _pyenv_virtualenv_hook() {
159192
fi
160193
_PYENV_VH_PWD="\${PWD}"
161194
_PYENV_VH_VERSION="\${PYENV_VERSION-}"
162-
_PYENV_VH_LOCAL="\${pvh_local}"
163-
_PYENV_VH_GLOBAL="\${pvh_global}"
164195
_PYENV_VH_VENV="\${VIRTUAL_ENV-}"
196+
_PYENV_VH_MARKER="\${PYENV_ROOT}/.pyenv-vh-marker-\$\$"
197+
: > "\${_PYENV_VH_MARKER}"
165198
return \$ret
166199
};
167200
typeset -g -a precmd_functions

0 commit comments

Comments
 (0)