diff --git a/e2e/lockfile/test_lockfile_idiomatic_version_file b/e2e/lockfile/test_lockfile_idiomatic_version_file index ed85c3497f..aad79472df 100644 --- a/e2e/lockfile/test_lockfile_idiomatic_version_file +++ b/e2e/lockfile/test_lockfile_idiomatic_version_file @@ -48,6 +48,29 @@ idiomatic_version_file_enable_tools = ["dummy"] lockfile = true EOF +# Keep mise.toml and .mise/config.toml together to verify that an idiomatic +# .dummy-version maps to .mise/mise.lock instead of the root mise.lock. +mkdir -p .mise +cat <<'EOF' >.mise/config.toml +[settings] +idiomatic_version_file_enable_tools = ["dummy"] +lockfile = true +EOF + +echo "1" >.dummy-version + +output=$(mise lock --dry-run --platform "$PLATFORM" 2>&1) +assert_contains "echo '$output'" ".mise/mise.lock" +assert_not_contains "echo '$output'" "for ~/workdir/mise.lock" + +rm -rf mise.toml .mise .dummy-version + +cat <<'EOF' >mise.toml +[settings] +idiomatic_version_file_enable_tools = ["dummy"] +lockfile = true +EOF + echo "1" >.dummy-version touch mise.lock diff --git a/src/lockfile.rs b/src/lockfile.rs index 79035aee5c..6c46550d3d 100644 --- a/src/lockfile.rs +++ b/src/lockfile.rs @@ -898,6 +898,9 @@ pub fn lockfile_path_for_config(config_path: &Path) -> (PathBuf, bool) { /// /// Idiomatic version files are not config files themselves, so their lock entries /// belong to the nearest active mise config root that contains the version file. +/// If multiple base configs share that root, the later entry in config_files wins +/// so colocated configs have a deterministic lockfile target, such as +/// .mise/config.toml mapping .dummy-version to .mise/mise.lock instead of mise.lock. pub fn lockfile_path_for_tool_source( config: &Config, source: &ToolSource, @@ -907,8 +910,9 @@ pub fn lockfile_path_for_tool_source( ToolSource::IdiomaticVersionFile(path) => config .config_files .iter() - .filter(|(_, cf)| cf.source().is_mise_toml()) - .filter_map(|(config_path, cf)| { + .enumerate() + .filter(|(_, (_, cf))| cf.source().is_mise_toml()) + .filter_map(|(idx, (config_path, cf))| { let root = cf.project_root().unwrap_or_else(|| cf.config_root()); let is_base = !is_local_config(config_path) && extract_env_from_config_path(config_path).is_none(); @@ -916,12 +920,14 @@ pub fn lockfile_path_for_tool_source( ( root.components().count(), is_base, + // Tie-break same-root base configs by config_files order. + idx, lockfile_path_for_config(config_path), ) }) }) - .max_by_key(|(root_depth, is_base, _)| (*root_depth, *is_base)) - .map(|(_, _, lockfile)| lockfile), + .max_by_key(|(root_depth, is_base, idx, _)| (*root_depth, *is_base, *idx)) + .map(|(_, _, _, lockfile)| lockfile), _ => None, } }