Repository navigation
fix(install): switch backends into a new installation, and resolve CLI-named versions without guessing #13957
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jdx
merged 19 commits into
claude/project-thread-htk77e-3-installs-migrate
from
claude/project-thread-htk77e-4-layout-gaps
Oct 5, 2026
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
b41b24f
fix(install): switch backends into a new installation, and resolve CL…
jdx 73a8e73
fix(install): check every installs root on backend switch, apply opti…
jdx 18dad59
fix(install): never install into or select a variant standing in for …
jdx efbbd67
fix(backends): find the old backend's version path when only one back…
jdx 5ebb771
fix(install): do not cache a variant standing in for a bare command-l…
jdx 846b888
fix(install): never take an installation built from another dependenc…
jdx 4e6aa14
fix(backends): find the old backend's installation by its receipt whe…
jdx 6dcb2a7
fix(install): do not run an always-run postinstall hook in a variant …
jdx 913b787
fix(install): only take a variant of the same tool for a bare version
jdx 009387f
perf(backends): list installations once, and only for switched versio…
jdx 848e603
fix(backends): compare versions the way installations name them when …
jdx ec7d683
fix(install): tell a bare version's settings from request options whe…
jdx 292cf2a
fix(install): let an installation stand for a command-line version wh…
jdx 58a3fc1
fix(install): only a version with no options of its own stands for an…
jdx 9bb9392
fix(install): count options written inline on the command line as the…
jdx bbd7741
fix(install): find a lone variant in a shared root whose catalog is gone
jdx e0b489d
fix(install): count variants by the options they were made with, not …
jdx f02d5f5
fix(install): take an option set's selected copy only if it is one of…
jdx 5b5803a
fix(install): never take another copy of an option set whose selected…
jdx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| #!/usr/bin/env bash | ||
| # A version named on the command line carries no install options, so under the | ||
| # identity install layout it can mean an installation a configuration made with | ||
| # options. `mise where` uses one such installation when it is the only one, and | ||
| # lists them instead of guessing when there are several. | ||
| export MISE_EXPERIMENTAL=1 | ||
| export MISE_INSTALL_LAYOUT=identity | ||
| work="$PWD" | ||
|
|
||
| mkdir env opt | ||
| cat >env/mise.toml <<'EOF2' | ||
| [tools] | ||
| dummy = { version = "2.0.0", install_env = { DUMMY_VARIANT = "musl" } } | ||
| EOF2 | ||
| cat >opt/mise.toml <<'EOF2' | ||
| [tools] | ||
| dummy = { version = "2.0.0", flavor = "full" } | ||
| EOF2 | ||
|
|
||
| echo "=== the only installation of the version is found ===" | ||
| assert "mise -C $work/env install" | ||
| env_dir="$(mise -C "$work/env" where dummy)" | ||
| assert "mise where dummy@2.0.0" "$env_dir" | ||
|
|
||
| echo "=== several are listed, not guessed between ===" | ||
| assert "mise -C $work/opt install" | ||
| opt_dir="$(mise -C "$work/opt" where dummy)" | ||
| assert_fail "mise where dummy@2.0.0" "installed with several sets of options" | ||
| assert_fail "mise where dummy@2.0.0" "$(basename "$opt_dir")" | ||
| assert_fail "mise where dummy@2.0.0" "$(basename "$env_dir")" | ||
|
|
||
| echo "=== naming the options finds the variant ===" | ||
| assert "mise where 'dummy[flavor=full]@2.0.0'" "$opt_dir" | ||
| assert "mise -C $work/opt where dummy@2.0.0" "$opt_dir" | ||
|
|
||
| echo "=== a lone variant stands for the bare version in every command ===" | ||
| mkdir solo | ||
| cat >solo/mise.toml <<'EOF2' | ||
| [tools] | ||
| dummy = { version = "1.0.0", flavor = "full" } | ||
| EOF2 | ||
| assert "mise -C $work/solo install" | ||
| solo_dir="$(mise -C "$work/solo" where dummy)" | ||
| assert "mise where dummy@1.0.0" "$solo_dir" | ||
| assert "mise bin-paths dummy@1.0.0 | xargs realpath" "$(realpath "$solo_dir/bin")" | ||
| assert "mise which dummy --tool dummy@1.0.0 | xargs realpath" "$(realpath "$solo_dir/bin/dummy")" | ||
| assert_contains "mise x dummy@1.0.0 -- dummy" "This is Dummy 1.0.0!" | ||
| assert_contains "mise install dummy@1.0.0 2>&1" "already installed" | ||
| assert "test \"\$(mise installs ls dummy --json | jq '[.[] | select(.version == \"1.0.0\")] | length')\" -eq 1" | ||
| # a forced install of the bare version makes its own installation, which it then uses | ||
| assert "mise install --force dummy@1.0.0" | ||
| assert "test \"\$(mise installs ls dummy --json | jq '[.[] | select(.version == \"1.0.0\")] | length')\" -eq 2" | ||
| plain_dir="$(mise where dummy@1.0.0)" | ||
| [[ $plain_dir != "$solo_dir" ]] || fail "the bare version still resolves to the variant" | ||
| assert "mise -C $work/solo where dummy" "$solo_dir" | ||
|
|
||
| echo "=== a lone variant that has to be installed again is not installed over for the bare version ===" | ||
| mkdir solo2 | ||
| cat >solo2/mise.toml <<'EOF2' | ||
| [tools] | ||
| tiny = { version = "3.1.0", flavor = "full" } | ||
| EOF2 | ||
| assert "mise -C $work/solo2 install" | ||
| solo2_dir="$(mise -C "$work/solo2" where tiny)" | ||
| assert "mise where tiny@3.1.0" "$solo2_dir" | ||
| # as if a refresh of the variant had been interrupted | ||
| mkdir -p "$MISE_CACHE_DIR/tiny/$(basename "$solo2_dir")" | ||
| touch "$MISE_CACHE_DIR/tiny/$(basename "$solo2_dir")/incomplete" | ||
| assert "mise install tiny@3.1.0" | ||
| bare_dir="$(mise where tiny@3.1.0)" | ||
| [[ $bare_dir != "$solo2_dir" ]] || fail "the bare version was installed into the variant" | ||
| # the variant is still the project's, once its own refresh is done | ||
| rm "$MISE_CACHE_DIR/tiny/$(basename "$solo2_dir")/incomplete" | ||
| assert "mise -C $work/solo2 where tiny" "$solo2_dir" | ||
| assert "mise installs ls tiny --json | jq -r '.[] | select(.selected and .version == \"3.1.0\") | .dir' | sort | tr '\n' ' '" "$(printf '%s\n' "$bare_dir" "$solo2_dir" | sort | tr '\n' ' ')" | ||
|
|
||
| echo "=== a postinstall hook that always runs never runs in a variant standing in ===" | ||
| mkdir variant hooked | ||
| cat >variant/mise.toml <<'EOF2' | ||
| [tools] | ||
| tiny = { version = "3.0.0", flavor = "full" } | ||
| EOF2 | ||
| assert "mise -C $work/variant install" | ||
| variant_dir="$(mise -C "$work/variant" where tiny)" | ||
| cat >hooked/mise.toml <<'EOF2' | ||
| [tools] | ||
| tiny = { version = "3.0.0", postinstall = { run = "touch \"$MISE_TOOL_INSTALL_PATH/hooked\"", when = "always" } } | ||
| EOF2 | ||
| assert "mise -C $work/hooked install tiny@3.0.0" | ||
| assert "test ! -e '$variant_dir/hooked'" | ||
| assert "test -e \"\$(mise -C '$work/hooked' where tiny@3.0.0)/hooked\"" | ||
|
|
||
| echo "=== another tool aliasing the same backend never stands in ===" | ||
| mkdir aliased | ||
| cat >aliased/mise.toml <<EOF2 | ||
| [tool_alias] | ||
| tinier = "$(mise tool tiny --backend)" | ||
|
|
||
| [tools] | ||
| tinier = { version = "2.1.0", flavor = "full" } | ||
| EOF2 | ||
| assert "mise -C $work/aliased install" | ||
| tinier_dir="$(mise -C "$work/aliased" where tinier)" | ||
| [[ $tinier_dir == "$MISE_DATA_DIR/installs/"tiny-* ]] || fail "$tinier_dir is not an identity installation" | ||
| assert_fail "mise where tiny@2.1.0" | ||
|
|
||
| echo "=== a version a configuration gives options never takes an installation with more ===" | ||
| mkdir env2 more | ||
| cat >env2/mise.toml <<'EOF2' | ||
| [tools] | ||
| tiny = { version = "1.0.0", install_env = { TINY_BUILD = "1" } } | ||
| EOF2 | ||
| cat >more/mise.toml <<'EOF2' | ||
| [tools] | ||
| tiny = { version = "1.0.0", install_env = { TINY_BUILD = "1" }, flavor = "full" } | ||
| EOF2 | ||
| assert "mise -C $work/more install" | ||
| assert_fail "mise -C $work/env2 where tiny@1.0.0" | ||
| # while the version named alone does find it | ||
| assert "mise where tiny@1.0.0" "$(mise -C "$work/more" where tiny)" | ||
| # options written on the command line are the version's own too | ||
| assert_fail "mise where 'tiny[flavor=full]@1.0.0'" | ||
|
|
||
| echo "=== a lone variant in a shared root whose catalog is gone still stands in ===" | ||
| mkdir sharedvar | ||
| cat >sharedvar/mise.toml <<'EOF2' | ||
| [tools] | ||
| tiny = { version = "1.0.1", flavor = "full" } | ||
| EOF2 | ||
| assert "MISE_INSTALLS_DIR='$HOME/shared-variants' mise -C $work/sharedvar install" | ||
| mv "$HOME/shared-variants/.mise" "$HOME/shared-variants/.mise.lost" | ||
| assert_matches "MISE_SHARED_INSTALL_DIRS='$HOME/shared-variants' mise where tiny@1.0.1" "^$HOME/shared-variants/tiny-" | ||
| # a second copy made with the same options is the same variant: its selection says which | ||
| export MISE_SHARED_INSTALL_DIRS="$HOME/shared-variants" | ||
| assert "mise -C $work/sharedvar install --force" | ||
| own_copy="$(mise -C "$work/sharedvar" where tiny)" | ||
| [[ $own_copy == "$MISE_DATA_DIR/installs/"tiny-* ]] || fail "$own_copy is not in the user's root" | ||
| assert "mise where tiny@1.0.1" "$own_copy" | ||
| # with the selected copy gone, the other copy is not taken in its place | ||
| rm -rf "$own_copy" | ||
| assert_fail "mise where tiny@1.0.1" | ||
| unset MISE_SHARED_INSTALL_DIRS | ||
|
|
||
| echo "=== a version named with options a configuration sets never stands in for another variant ===" | ||
| mkdir minimal | ||
| cat >minimal/mise.toml <<'EOF2' | ||
| [tools] | ||
| dummy = { version = "1.1.0", flavor = "minimal" } | ||
| EOF2 | ||
| assert "mise -C $work/minimal install" | ||
| mkdir full | ||
| cat >full/mise.toml <<'EOF2' | ||
| [tools] | ||
| dummy = { version = "1.1.0", flavor = "full" } | ||
| EOF2 | ||
| assert_fail "mise -C $work/full where dummy@1.1.0" | ||
| assert_fail "mise -C $work/full where dummy 1.1.0" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| #!/usr/bin/env bash | ||
| # `mise backends switch` under the identity install layout: the new backend's | ||
| # installation is a different one from the old backend's, so the switch installs | ||
| # it (instead of finding the version "already installed"), and the version link | ||
| # moves to it. hk moved from Aqua to Packslip at 1.58.1. | ||
| export MISE_EXPERIMENTAL=1 | ||
| export MISE_INSTALL_LAYOUT=identity | ||
| export MISE_LOCKFILE=1 | ||
| export MISE_MINIMUM_RELEASE_AGE=0 | ||
| detect_platform | ||
| installs="$MISE_DATA_DIR/installs" | ||
|
|
||
| cat <<'EOF2' >mise.toml | ||
| [tools] | ||
| hk = "1" | ||
| EOF2 | ||
| cat <<'EOF2' >mise.lock | ||
| lockfile_version = 2 | ||
|
|
||
| [[tools.hk]] | ||
| version = "1.58.1" | ||
| backend = "aqua:jdx/hk" | ||
| specifiers = ["1"] | ||
| EOF2 | ||
|
|
||
| assert_contains "mise install 2>&1" "mise backends switch hk" | ||
| old_dir="$(mise where hk)" | ||
| assert_contains "cat '$old_dir/.mise-install.toml'" 'backend = "aqua:jdx/hk"' | ||
|
|
||
| mise backends switch hk | ||
| assert_contains "cat mise.lock" 'backend = "packslip:github.com/jdx/hk"' | ||
| new_dir="$(mise where hk)" | ||
| [[ $new_dir != "$old_dir" ]] || fail "the switch kept the aqua installation $old_dir" | ||
| assert_contains "cat '$new_dir/.mise-install.toml'" 'backend = "packslip:github.com/jdx/hk"' | ||
| assert "readlink '$installs/hk/1.58.1'" "../$(basename "$new_dir")" | ||
| assert_contains "mise exec -- hk --version" "hk 1.58.1" | ||
| # the old backend's installation is left for prune, not deleted | ||
| assert "test -f '$old_dir/.mise-install.toml'" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.