From cd84623ab28093f8b13382001f590a3a1e36513b Mon Sep 17 00:00:00 2001 From: Thanniru Sai Teja Date: Thu, 3 Sep 2026 12:24:16 +0530 Subject: [PATCH] [Perf] Optimize nvm_tree_contains_path with in-memory POSIX parent-walk Replace looping subshell process forks (dirname) in nvm_tree_contains_path with case-guarded in-memory POSIX parameter expansion () and exact string equality comparisons. Ensures literal string matching for glob metacharacters (*, ?, []) and full zsh compatibility across all platforms. --- nvm.sh | 41 ++++++++++++++++----- test/fast/Unit tests/nvm_tree_contains_path | 7 ++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/nvm.sh b/nvm.sh index cc636501..429911cb 100755 --- a/nvm.sh +++ b/nvm.sh @@ -547,6 +547,9 @@ 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. nvm_tree_contains_path() { local tree tree="${1-}" @@ -558,16 +561,36 @@ nvm_tree_contains_path() { return 2 fi - local previous_pathdir - previous_pathdir="${node_path}" - local pathdir - pathdir=$(dirname "${previous_pathdir}") - while [ "${pathdir}" != '' ] && [ "${pathdir}" != '.' ] && [ "${pathdir}" != '/' ] && - [ "${pathdir}" != "${tree}" ] && [ "${pathdir}" != "${previous_pathdir}" ]; do - previous_pathdir="${pathdir}" - pathdir=$(dirname "${previous_pathdir}") + local clean_tree + clean_tree="${tree}" + + # Strip trailing slashes in shell memory (e.g., "dir//" -> "dir") + while [ "${clean_tree}" != "${clean_tree%/}" ]; do + clean_tree="${clean_tree%/}" done - [ "${pathdir}" = "${tree}" ] + if [ -z "${clean_tree}" ]; then + 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. + local pathdir + pathdir="${node_path}" + while [ "${pathdir}" != '' ] && [ "${pathdir}" != '.' ] && [ "${pathdir}" != '/' ] && + [ "${pathdir}" != "${clean_tree}" ]; do + case "${pathdir}" in + */*) + pathdir="${pathdir%/*}" + if [ -z "${pathdir}" ]; then + pathdir='/' + fi + ;; + *) + break + ;; + esac + done + [ "${pathdir}" = "${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 efaa9088..1dec1cde 100755 --- a/test/fast/Unit tests/nvm_tree_contains_path +++ b/test/fast/Unit tests/nvm_tree_contains_path @@ -29,4 +29,11 @@ nvm_tree_contains_path tmp2 tmp2/node || die '"tmp2" should contain "tmp2/node"' nvm_tree_contains_path tmp2 tmp/node && die '"tmp2" should not contain "tmp/node"' +nvm_tree_contains_path tmp/ tmp/node || die '"tmp/" should contain "tmp/node"' +nvm_tree_contains_path tmp// tmp/node || die '"tmp//" should contain "tmp/node"' +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"' + cleanup