diff --git a/libexec/pyenv-version-file b/libexec/pyenv-version-file index 91d8b0d3..e9fba30b 100755 --- a/libexec/pyenv-version-file +++ b/libexec/pyenv-version-file @@ -8,12 +8,28 @@ target_dir="$1" find_local_version_file() { local root="$1" - while ! [[ "$root" =~ ^//[^/]*$ ]]; do - if [ -f "${root}/.python-version" ]; then - echo "${root}/.python-version" + # Nonexistent paths are UB as of this writing. + # Relative ones cause a failure but absolute ones don't + [[ $root != /* ]] && root=$(CDPATH= cd -- "$root" && pwd) + # 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 + root="${root%/}" + # 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 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" +}