diff --git a/nvm.sh b/nvm.sh index 45f9a0fa..1e9f6da1 100755 --- a/nvm.sh +++ b/nvm.sh @@ -212,6 +212,23 @@ nvm_has_system_iojs() { [ "$(nvm deactivate >/dev/null 2>&1 && command -v iojs)" != '' ] } +nvm_is_installed_exact_version() { + case "${1-}" in + v*) ;; + *) return 1 ;; + esac + case "${1#v}" in + *[!0-9.]* | .* | *. | *..* | *.*.*.*) return 1 ;; + *.*.*) ;; + *) return 1 ;; + esac + # an alias with this exact name takes precedence, as it does in `nvm_version` + if [ -e "$(nvm_alias_path)/${1}" ]; then + return 1 + fi + nvm_is_version_installed "${1}" +} + nvm_is_version_installed() { if [ -z "${1-}" ]; then return 1 @@ -4536,6 +4553,8 @@ nvm() { IS_VERSION_FROM_NVMRC=0 local NVM_WRITE_TO_NVMRC NVM_WRITE_TO_NVMRC=0 + local NVM_USE_EXACT_INSTALLED + NVM_USE_EXACT_INSTALLED=0 while [ $# -ne 0 ]; do case "$1" in @@ -4576,6 +4595,9 @@ nvm() { nvm_err 'Please see `nvm --help` or https://github.com/nvm-sh/nvm#nvmrc for more information.' return 127 fi + elif nvm_is_installed_exact_version "${PROVIDED_VERSION}"; then + VERSION="${PROVIDED_VERSION}" + NVM_USE_EXACT_INSTALLED=1 else VERSION="$(nvm_match_version "${PROVIDED_VERSION}")" fi @@ -4617,9 +4639,8 @@ nvm() { nvm_ensure_version_installed "${PROVIDED_VERSION}" "${IS_VERSION_FROM_NVMRC}" fi return 3 - # This nvm_ensure_version_installed call can be a performance bottleneck - # on shell startup. Perhaps we can optimize it away or make it faster. - elif ! nvm_ensure_version_installed "${VERSION}" "${IS_VERSION_FROM_NVMRC}"; then + # an exact, already-installed version needs no further resolution or checks + elif [ "${NVM_USE_EXACT_INSTALLED}" -ne 1 ] && ! nvm_ensure_version_installed "${VERSION}" "${IS_VERSION_FROM_NVMRC}"; then return $? fi @@ -5241,7 +5262,7 @@ nvm() { nvm_echo nvm_err nvm_grep nvm_cd \ nvm_die_on_prefix nvm_get_make_jobs nvm_get_minor_version \ nvm_has_solaris_binary nvm_is_merged_node_version \ - nvm_is_natural_num nvm_is_version_installed nvm_validate_install \ + nvm_is_natural_num nvm_is_installed_exact_version nvm_is_version_installed nvm_validate_install \ nvm_install_lock_name nvm_acquire_install_lock nvm_release_install_lock \ nvm_list_aliases nvm_make_alias nvm_print_alias_file nvm_print_alias_path \ nvm_print_default_alias nvm_print_formatted_alias nvm_resolve_local_alias \ diff --git a/test/fast/Unit tests/nvm use skips re-resolving an exact installed version b/test/fast/Unit tests/nvm use skips re-resolving an exact installed version new file mode 100755 index 00000000..41f83445 --- /dev/null +++ b/test/fast/Unit tests/nvm use skips re-resolving an exact installed version @@ -0,0 +1,31 @@ +#!/bin/sh + +cleanup () { + rm -rf "$NVM_DIR" + unset -f die cleanup nvm_version +} +die () { echo "$@" ; cleanup ; exit 1; } + +: nvm.sh +\. ../../../nvm.sh + +NVM_DIR="$(mktemp -d)" +NODE_BIN="$NVM_DIR/versions/node/v12.0.0/bin" +mkdir -p "$NODE_BIN" +printf '#!/bin/sh\necho v12.0.0\n' > "$NODE_BIN/node" +chmod +x "$NODE_BIN/node" + +# the slow path resolves through nvm_version; make it fail so only the fast path can succeed +nvm_version() { + return 42 +} + +nvm use --silent v12.0.0 || die 'nvm use with an exact, installed version should not need nvm_version' +case ":${PATH}:" in + *":${NODE_BIN}:"*) ;; + *) die "nvm use did not put ${NODE_BIN} on PATH; got ${PATH}" ;; +esac + +! nvm use --silent v12 2>/dev/null || die 'nvm use with a partial version should still resolve it through nvm_version' + +cleanup diff --git a/test/fast/Unit tests/nvm_is_installed_exact_version b/test/fast/Unit tests/nvm_is_installed_exact_version new file mode 100755 index 00000000..10496773 --- /dev/null +++ b/test/fast/Unit tests/nvm_is_installed_exact_version @@ -0,0 +1,58 @@ +#!/bin/sh + +cleanup () { + rm -rf "$NVM_DIR" + unset -f die cleanup + unset NVM_DIR NODE_PATH VERSION +} +die () { echo "$@" ; cleanup ; exit 1; } + +: nvm.sh +\. ../../../nvm.sh + +set -ex + +NVM_DIR=$(mktemp -d) +NODE_PATH="$NVM_DIR/versions/node" +mkdir -p "$NODE_PATH" +if [ -z "$NODE_PATH" ]; then + die 'Unable to create temporary folder' +fi + +# nvm_is_installed_exact_version is available +type nvm_is_installed_exact_version > /dev/null 2>&1 || die 'nvm_is_installed_exact_version is not available' + +# nvm_is_installed_exact_version with no parameter fails +! nvm_is_installed_exact_version || die 'nvm_is_installed_exact_version without parameter should fail' + +# an exact version that is not installed fails +! nvm_is_installed_exact_version 'v12.0.0' || die 'v12.0.0 is not installed and should fail' + +# an exact version whose node binary is not executable fails +mkdir -p "$NODE_PATH/v12.0.0/bin" +touch "$NODE_PATH/v12.0.0/bin/node" +! nvm_is_installed_exact_version 'v12.0.0' || die 'v12.0.0 with a non-executable node should fail' + +# an exact, installed version succeeds +chmod +x "$NODE_PATH/v12.0.0/bin/node" +nvm_is_installed_exact_version 'v12.0.0' || die 'v12.0.0 is installed and should succeed' + +# something shaped almost like vX.Y.Z fails, even when a directory by that name is installed +for VERSION in 'v12.0.0.0' 'v12.0.0.' 'v12..0' 'v.12.0' 'v12.0'; do + mkdir -p "$NODE_PATH/$VERSION/bin" + touch "$NODE_PATH/$VERSION/bin/node" + chmod +x "$NODE_PATH/$VERSION/bin/node" + ! nvm_is_installed_exact_version "$VERSION" || die "$VERSION is not an exact version and should fail, even when installed" +done + +# anything that is not an exact vX.Y.Z fails, even if it would resolve to an installed version +for VERSION in '12.0.0' '12' 'v12' 'v12.0' 'v12.0.0-rc.1' 'node' 'stable' 'lts/*' 'iojs-v1.0.0' 'system' 'default'; do + ! nvm_is_installed_exact_version "$VERSION" || die "$VERSION is not an exact version and should fail" +done + +# an alias with the exact same name takes precedence, so the fast path must not apply +mkdir -p "$(nvm_alias_path)" +echo 'v12.0.0' > "$(nvm_alias_path)/v12.0.0" +! nvm_is_installed_exact_version 'v12.0.0' || die 'an alias named v12.0.0 should take precedence' + +cleanup