diff --git a/nvm.sh b/nvm.sh index bb461c1e..45f9a0fa 100755 --- a/nvm.sh +++ b/nvm.sh @@ -552,9 +552,8 @@ else fi unset NVM_SCRIPT_SOURCE 2>/dev/null -# Performs pure in-memory POSIX path containment checking without subshell process forks. -# Uses case-guarded ${pathdir%/*} for parent-walk and exact string equality (=) to ensure literal matching -# for directory names containing glob metacharacters (*, ?, []) across all shells including zsh. +# Walks parent paths without subshells, comparing names literally before checking +# filesystem identity to account for case-insensitive paths and directory symlinks. nvm_tree_contains_path() { local tree tree="${1-}" @@ -577,12 +576,11 @@ nvm_tree_contains_path() { clean_tree='/' fi - # Pure in-memory POSIX parent-walk using parameter expansion instead of subshell dirname forks. - # Uses literal string equality [ "${pathdir}" = "${clean_tree}" ] to prevent glob expansion bugs. + # Keep literal matching for paths that do not exist, including glob characters. local pathdir pathdir="${node_path}" while [ "${pathdir}" != '' ] && [ "${pathdir}" != '.' ] && [ "${pathdir}" != '/' ] && - [ "${pathdir}" != "${clean_tree}" ]; do + [ "${pathdir}" != "${clean_tree}" ] && ! [ "${pathdir}" -ef "${clean_tree}" ]; do case "${pathdir}" in */*) pathdir="${pathdir%/*}" @@ -595,7 +593,7 @@ nvm_tree_contains_path() { ;; esac done - [ "${pathdir}" = "${clean_tree}" ] + [ "${pathdir}" = "${clean_tree}" ] || [ "${pathdir}" -ef "${clean_tree}" ] } nvm_find_project_dir() { diff --git a/test/fast/Unit tests/nvm_tree_contains_path b/test/fast/Unit tests/nvm_tree_contains_path index 1dec1cde..207320ee 100755 --- a/test/fast/Unit tests/nvm_tree_contains_path +++ b/test/fast/Unit tests/nvm_tree_contains_path @@ -1,6 +1,13 @@ #!/bin/sh cleanup () { + rm tmp-alias + if [ "${CASE_SENSITIVE-}" = '1' ]; then + rm tmp/TREE/node + rmdir tmp/TREE + fi + rm tmp/tree/node + rmdir tmp/tree rm tmp/node rmdir tmp rm tmp2/node @@ -15,6 +22,15 @@ mkdir -p tmp touch tmp/node mkdir -p tmp2 touch tmp2/node +mkdir -p tmp/tree +touch tmp/tree/node +ln -s tmp tmp-alias +CASE_SENSITIVE=0 +if [ ! -d tmp/TREE ]; then + CASE_SENSITIVE=1 + mkdir tmp/TREE + touch tmp/TREE/node +fi [ "$(nvm_tree_contains_path 2>&1)" = "both the tree and the node path are required" ] || die 'incorrect error message with no args' [ "$(nvm_tree_contains_path > /dev/null 2>&1 ; echo $?)" = "2" ] || die 'incorrect error code with no args' @@ -36,4 +52,18 @@ nvm_tree_contains_path / /tmp/node || die '"/" should contain "/tmp/node"' nvm_tree_contains_path "tmp[glob]" "tmp[glob]/node" || die '"tmp[glob]" should contain "tmp[glob]/node"' nvm_tree_contains_path "tmp" "tmp[glob]/node" && die '"tmp" should not contain "tmp[glob]/node"' +if [ "${CASE_SENSITIVE}" = '1' ]; then + nvm_tree_contains_path tmp/tree tmp/TREE/node && die 'case-sensitive directories should stay distinct' + nvm_tree_contains_path tmp/TREE tmp/tree/node && die 'case-sensitive directories should stay distinct' +else + nvm_tree_contains_path tmp/tree tmp/TREE/node || die 'case-insensitive directories should match' + nvm_tree_contains_path tmp/TREE tmp/tree/node || die 'case-insensitive directories should match' +fi + +nvm_tree_contains_path tmp-alias tmp/node || die 'a symlink to the tree should contain its node' +nvm_tree_contains_path tmp tmp-alias/node || die 'the tree should contain a node reached through a symlink' +nvm_tree_contains_path tmp-alias tmp2/node && die 'a symlink should not contain a sibling tree' + +nvm_tree_contains_path nonexistent NONEXISTENT/node && die 'missing directories should not be case folded' + cleanup