[Fix] nvm_hash_reset: only run hash -r where the shell supports it

Under bash's `set +h`, `hash -r` errors with "hash: hashing disabled",
which is then printed on every `nvm use`, including the one at shell startup.

Guarding on `$-` alone would be wrong:
`zsh` never puts `h` in `$-` - there `h` is `HIST_IGNORE_DUPS` -
and `dash` has no `h` flag at all,
so it would skip the call in 3 of the 4 shells the fast suite runs.

Gate on the shell instead, and skip only where the call fails:
bash under `set +h`, and `ksh93`, whose `hash` takes no `-r` and whose
usage error aborts `nvm use` before it can export `NVM_BIN`.
`ksh` is detected by probing rather than by version,
so `mksh` and `pdksh`, which do accept `-r`, keep getting the call.

The `${-#*h}` test is from #3910.

Refs #2065
Refs #3247
This commit is contained in:
Jordan Harband
2026-09-04 09:16:10 -07:00
parent 80e700ec21
commit aeace4606f
4 changed files with 117 additions and 3 deletions
+16 -3
View File
@@ -1117,6 +1117,19 @@ nvm_change_path() {
fi
}
nvm_hash_reset() {
# under `set +h` there is no cache to clear, and `hash -r` errors
if [ -n "${BASH_VERSION-}" ] && [ "${-#*h}" = "$-" ]; then
return 0
fi
# `ksh93`'s `hash` takes no `-r` and its usage error is fatal; probing in a
# subshell contains it, and spares the `ksh` variants that do accept `-r`
if [ -n "${KSH_VERSION-}" ] && ! ( \hash -r ) >/dev/null 2>&1; then
return 0
fi
\hash -r
}
nvm_binary_available() {
# binaries started with node 0.8.6
nvm_version_greater_than_or_equal_to "$(nvm_strip_iojs_prefix "${1-}")" v0.8.6
@@ -4228,7 +4241,7 @@ nvm() {
fi
else
export PATH="${NEWPATH}"
\hash -r
nvm_hash_reset
if [ "${NVM_SILENT:-0}" -ne 1 ]; then
nvm_echo "${NVM_DIR}/*/bin removed from \${PATH}"
fi
@@ -4373,7 +4386,7 @@ nvm() {
export MANPATH
fi
export PATH
\hash -r
nvm_hash_reset
export NVM_BIN="${NVM_VERSION_DIR}/bin"
export NVM_INC="${NVM_VERSION_DIR}/include/node"
if [ "${NVM_SYMLINK_CURRENT-}" = true ]; then
@@ -4980,7 +4993,7 @@ nvm() {
nvm_echo_with_colors nvm_err_with_colors \
nvm_get_artifact_compression nvm_install_binary_extract nvm_extract_tarball \
nvm_process_nvmrc nvm_process_nvmrc_content nvm_nvmrc_invalid_msg \
nvm_write_nvmrc \
nvm_write_nvmrc nvm_hash_reset \
>/dev/null 2>&1
unset NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \
NVM_CD_FLAGS NVM_BIN NVM_INC NVM_MAKE_JOBS NVM_INSTALL_LOCK \
@@ -0,0 +1,45 @@
#!/bin/bash
# the `bash` shebang is deliberate: only bash can exhibit the bug, so pinning
# it keeps this meaningful in every job of the shell matrix
die () { echo "$@" ; cleanup ; exit 1; }
cleanup () {
set -h
rm -f "${OUTPUT_FILE-}"
}
export NVM_DIR="$(cd ../.. && pwd)"
: nvm.sh
\. ../../nvm.sh
\. ../common.sh
make_fake_node v0.2.3 || die 'unable to make fake node'
OUTPUT_FILE="$(mktemp)"
set +h
# not in a command substitution, so the `PATH` it sets survives to `nvm deactivate`
nvm use --delete-prefix v0.2.3 > "${OUTPUT_FILE}" 2>&1
OUTPUT="$(cat "${OUTPUT_FILE}")"
case "${OUTPUT}" in
*'hash: '*) die "nvm use complained about hashing: ${OUTPUT}" ;;
esac
case "${OUTPUT}" in
*'Now using node v0.2.3'*) ;;
*) die "nvm use did not activate v0.2.3: ${OUTPUT}" ;;
esac
nvm deactivate > "${OUTPUT_FILE}" 2>&1
OUTPUT="$(cat "${OUTPUT_FILE}")"
case "${OUTPUT}" in
*'hash: '*) die "nvm deactivate complained about hashing: ${OUTPUT}" ;;
esac
case "${OUTPUT}" in
*'removed from ${PATH}'*) ;;
*) die "nvm deactivate did not strip the nvm bin dir: ${OUTPUT}" ;;
esac
cleanup
@@ -0,0 +1,29 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../.. && pwd)"
: nvm.sh
\. ../../nvm.sh
\. ../common.sh
make_fake_node v0.2.3 || die 'unable to make fake node v0.2.3'
make_fake_node v0.10.1 || die 'unable to make fake node v0.10.1'
nvm use --delete-prefix v0.2.3 >/dev/null 2>&1 || die 'failed to activate v0.2.3'
# run `node` from this shell, so that the shell caches where it found it
node >/dev/null 2>&1 || die 'fake node v0.2.3 is not runnable'
nvm use --delete-prefix v0.10.1 >/dev/null 2>&1 || die 'failed to activate v0.10.1'
ACTUAL="$(node)"
[ "${ACTUAL}" = 'v0.10.1' ] || die "expected node v0.10.1 after switching versions, got '${ACTUAL}'"
nvm deactivate >/dev/null 2>&1 || die 'failed to deactivate'
RESOLVED="$(command -v node || true)"
case "${RESOLVED}" in
"${NVM_DIR}"/*) die "after deactivating, node still resolves inside \${NVM_DIR}: ${RESOLVED}" ;;
esac
+27
View File
@@ -0,0 +1,27 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
# also covers #3247, where `command hash -r` found a `hash` binary, not the builtin
OUTPUT="$(nvm_hash_reset 2>&1)"
RC=$?
[ ${RC} -eq 0 ] || die "nvm_hash_reset returned ${RC}: ${OUTPUT}"
[ -z "${OUTPUT}" ] || die "nvm_hash_reset was not silent: ${OUTPUT}"
# bash is the only supported shell that can turn hashing off
if [ -n "${BASH_VERSION-}" ]; then
set +h
OUTPUT="$(nvm_hash_reset 2>&1)"
RC=$?
set -h
[ ${RC} -eq 0 ] || die "with hashing disabled, nvm_hash_reset returned ${RC}: ${OUTPUT}"
[ -z "${OUTPUT}" ] || die "with hashing disabled, nvm_hash_reset was not silent: ${OUTPUT}"
ls >/dev/null 2>&1
hash -t ls >/dev/null 2>&1 || die 'expected `ls` to be hashed'
nvm_hash_reset || die 'with hashing enabled, nvm_hash_reset failed'
! hash -t ls >/dev/null 2>&1 || die 'nvm_hash_reset did not clear the hash table'
fi