1
0
mirror of https://github.com/nvm-sh/nvm.git synced 2026-10-23 06:44:48 +08:00

[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.
This commit is contained in:
Thanniru Sai Teja
2026-09-03 12:24:16 +05:30
committed by Jordan Harband
parent e361b93e4b
commit cd84623ab2
2 changed files with 39 additions and 9 deletions

41
nvm.sh
View File

@@ -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() {

View File

@@ -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