Skip to content

Commit c56e953

Browse files
committed
[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.
1 parent 10a277e commit c56e953

2 files changed

Lines changed: 37 additions & 7 deletions

File tree

‎nvm.sh‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,9 @@ else
544544
fi
545545
unset NVM_SCRIPT_SOURCE 2>/dev/null
546546

547+
# Performs pure in-memory POSIX path containment checking without subshell process forks.
548+
# Uses case-guarded ${pathdir%/*} for parent-walk and exact string equality (=) to ensure literal matching
549+
# for directory names containing glob metacharacters (*, ?, []) across all shells including zsh.
547550
nvm_tree_contains_path() {
548551
local tree
549552
tree="${1-}"
@@ -555,16 +558,36 @@ nvm_tree_contains_path() {
555558
return 2
556559
fi
557560

558-
local previous_pathdir
559-
previous_pathdir="${node_path}"
561+
local clean_tree
562+
clean_tree="${tree}"
563+
564+
# Strip trailing slashes in shell memory (e.g., "dir//" -> "dir")
565+
while [ "${clean_tree}" != "${clean_tree%/}" ]; do
566+
clean_tree="${clean_tree%/}"
567+
done
568+
if [ -z "${clean_tree}" ]; then
569+
clean_tree='/'
570+
fi
571+
572+
# Pure in-memory POSIX parent-walk using parameter expansion instead of subshell dirname forks.
573+
# Uses literal string equality [ "${pathdir}" = "${clean_tree}" ] to prevent glob expansion bugs.
560574
local pathdir
561-
pathdir=$(dirname "${previous_pathdir}")
575+
pathdir="${node_path}"
562576
while [ "${pathdir}" != '' ] && [ "${pathdir}" != '.' ] && [ "${pathdir}" != '/' ] &&
563-
[ "${pathdir}" != "${tree}" ] && [ "${pathdir}" != "${previous_pathdir}" ]; do
564-
previous_pathdir="${pathdir}"
565-
pathdir=$(dirname "${previous_pathdir}")
577+
[ "${pathdir}" != "${clean_tree}" ]; do
578+
case "${pathdir}" in
579+
*/*)
580+
pathdir="${pathdir%/*}"
581+
if [ -z "${pathdir}" ]; then
582+
pathdir='/'
583+
fi
584+
;;
585+
*)
586+
break
587+
;;
588+
esac
566589
done
567-
[ "${pathdir}" = "${tree}" ]
590+
[ "${pathdir}" = "${clean_tree}" ]
568591
}
569592

570593
nvm_find_project_dir() {

‎test/fast/Unit tests/nvm_tree_contains_path‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,11 @@ nvm_tree_contains_path tmp2 tmp2/node || die '"tmp2" should contain "tmp2/node"'
2929

3030
nvm_tree_contains_path tmp2 tmp/node && die '"tmp2" should not contain "tmp/node"'
3131

32+
nvm_tree_contains_path tmp/ tmp/node || die '"tmp/" should contain "tmp/node"'
33+
nvm_tree_contains_path tmp// tmp/node || die '"tmp//" should contain "tmp/node"'
34+
nvm_tree_contains_path / /tmp/node || die '"/" should contain "/tmp/node"'
35+
36+
nvm_tree_contains_path "tmp[glob]" "tmp[glob]/node" || die '"tmp[glob]" should contain "tmp[glob]/node"'
37+
nvm_tree_contains_path "tmp" "tmp[glob]/node" && die '"tmp" should not contain "tmp[glob]/node"'
38+
3239
cleanup

0 commit comments

Comments
 (0)