From 195b9fe940cf787c80215d5fb14dbad6127b1ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=98=E5=85=B8?= Date: Sat, 8 Aug 2026 20:35:35 +0300 Subject: [PATCH 1/3] version-file: Fix infinite loop for relative paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit find_local_version_file reduces its target with ${root%/*}, but that leaves a slash-less relative argument such as "." unchanged, leading to an infinite loop Signed-off-by: 付典 --- libexec/pyenv-version-file | 3 +++ test/version-file.bats | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/libexec/pyenv-version-file b/libexec/pyenv-version-file index 91d8b0d3..8b1fe30b 100755 --- a/libexec/pyenv-version-file +++ b/libexec/pyenv-version-file @@ -8,6 +8,9 @@ target_dir="$1" find_local_version_file() { local root="$1" + # Nonexistent paths are UB as of this writing. + # Relative ones cause a failure but absolute ones don't + [[ $root != /* ]] && root=$(CDPATH= cd -- "$root" && pwd) while ! [[ "$root" =~ ^//[^/]*$ ]]; do if [ -f "${root}/.python-version" ]; then echo "${root}/.python-version" diff --git a/test/version-file.bats b/test/version-file.bats index e2fb941c..3c0d40a2 100644 --- a/test/version-file.bats +++ b/test/version-file.bats @@ -73,3 +73,15 @@ create_file() { run pyenv-version-file "$PWD" assert_failure "" } + +@test "walks up beyond cwd for a relative path" { + create_file ".python-version" + mkdir -p project/subdir + cd project + # Run with CPU-time limit because this used to loop forever + run bash -c 'ulimit -t 5; pyenv-version-file .' + assert_success "${PYENV_TEST_DIR}/.python-version" + + run pyenv-version-file ./subdir + assert_success "${PYENV_TEST_DIR}/.python-version" +} From 48e70e1fe9b00c6d6b4e00997cc1422494c35442 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Mon, 10 Aug 2026 12:55:19 +0300 Subject: [PATCH 2/3] Only test for UNC paths every iteration if they are special --- libexec/pyenv-version-file | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/libexec/pyenv-version-file b/libexec/pyenv-version-file index 8b1fe30b..a0309a65 100755 --- a/libexec/pyenv-version-file +++ b/libexec/pyenv-version-file @@ -11,12 +11,24 @@ find_local_version_file() { # Nonexistent paths are UB as of this writing. # Relative ones cause a failure but absolute ones don't [[ $root != /* ]] && root=$(CDPATH= cd -- "$root" && pwd) - while ! [[ "$root" =~ ^//[^/]*$ ]]; do - if [ -f "${root}/.python-version" ]; then - echo "${root}/.python-version" + # Original Rbenv code supports UNC notaion for Cygwin/MinGW + # (https://github.com/rbenv/rbenv/pull/529) + # POSIX.1-2024 still allows to treat // in implementation-specific manner + # (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap04.html#tag_04_16) + # even though few UNIX variants do that + local unc; [[ $root =~ ^//[^/] && ! / -ef // ]] && unc=1 + # when testing root, $root is "" + while true; do + # don't test //.python-version if // is special + # as it's pointless and possibly very slow + # if it e.g. leads to a network search + [[ $unc && $root == / ]] && break + + if [[ -f $root/.python-version ]]; then + echo "$root/.python-version" return 0 fi - [ -n "$root" ] || break + [[ -n $root ]] || break root="${root%/*}" done return 1 From 1a591b3963a02ba7e3f9966b111578e61662ef21 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Mon, 10 Aug 2026 12:56:57 +0300 Subject: [PATCH 3/3] Avoid extra iteration for argument with a trailing slash --- libexec/pyenv-version-file | 1 + 1 file changed, 1 insertion(+) diff --git a/libexec/pyenv-version-file b/libexec/pyenv-version-file index a0309a65..e9fba30b 100755 --- a/libexec/pyenv-version-file +++ b/libexec/pyenv-version-file @@ -17,6 +17,7 @@ find_local_version_file() { # (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap04.html#tag_04_16) # even though few UNIX variants do that local unc; [[ $root =~ ^//[^/] && ! / -ef // ]] && unc=1 + root="${root%/}" # when testing root, $root is "" while true; do # don't test //.python-version if // is special