Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions e2e/cli/test_set
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ assert "mise set NEW_VAR=new_value" ""
assert_contains "cat mise.toml" "NEW_VAR"
assert_not_contains "cat mise.local.toml" "NEW_VAR"

# Test that mise set writes to the nearest config directory, not the lowest
# precedence TOML file across the entire hierarchy.
rm -rf parent
mkdir -p parent/myproject/inner
touch parent/mise.toml parent/myproject/mise.toml
assert "cd parent/myproject/inner && mise set SOMETHING=from_inner" ""
assert_contains "cat parent/myproject/mise.toml" "SOMETHING"
assert_not_contains "cat parent/mise.toml" "SOMETHING"
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved

# Test that ignored config directories are not selected as write targets.
ignored_child="$(pwd)/parent/myproject"
assert "cd parent/myproject/inner && MISE_IGNORED_CONFIG_PATHS=$ignored_child mise set IGNORED_CHILD=skipped" ""
assert_contains "cat parent/mise.toml" "IGNORED_CHILD"
assert_not_contains "cat parent/myproject/mise.toml" "IGNORED_CHILD"
rm -rf parent

# Test --stdin reads multiline value
assert "printf 'line1\nline2' | mise set --stdin MULTI_LINE" ""
assert "mise set MULTI_LINE" "line1
Expand Down
103 changes: 52 additions & 51 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1170,11 +1170,7 @@ pub fn load_config_paths(config_filenames: &[String], include_ignored: bool) ->
let mut config_files = dirs
.iter()
.flat_map(|dir| {
if !include_ignored
&& env::MISE_IGNORED_CONFIG_PATHS
.iter()
.any(|p| dir.starts_with(p))
{
if config_dir_is_ignored(dir, include_ignored) {
vec![]
} else {
config_filenames
Expand All @@ -1192,13 +1188,7 @@ pub fn load_config_paths(config_filenames: &[String], include_ignored: bool) ->
config_files
.into_iter()
.unique_by(|p| file::desymlink_path(p))
.filter(|p| {
if is_default_config_dir_override_filtered(p) {
return false;
}
include_ignored
|| !(config_file::is_ignored(&config_trust_root(p)) || config_file::is_ignored(p))
})
.filter(|p| !config_path_is_ignored(p, include_ignored))
.collect()
}

Expand Down Expand Up @@ -1302,11 +1292,7 @@ fn detect_auto_env_candidate_files() -> Vec<PathBuf> {
// that wouldn't be loaded even with auto_env enabled
found
.into_iter()
.filter(|p| {
!(is_default_config_dir_override_filtered(p)
|| config_file::is_ignored(&config_trust_root(p))
|| config_file::is_ignored(p))
})
.filter(|p| !config_path_is_ignored(p, false))
.collect()
}

Expand Down Expand Up @@ -1335,10 +1321,7 @@ pub async fn load_config_hierarchy_from_dir(
let mut config_files = dirs
.iter()
.flat_map(|dir| {
if env::MISE_IGNORED_CONFIG_PATHS
.iter()
.any(|p| dir.starts_with(p))
{
if config_dir_is_ignored(dir, false) {
vec![]
} else {
config_filenames
Expand All @@ -1357,12 +1340,7 @@ pub async fn load_config_hierarchy_from_dir(
let paths = config_files
.into_iter()
.unique_by(|p| file::desymlink_path(p))
.filter(|p| {
if is_default_config_dir_override_filtered(p) {
return false;
}
!(config_file::is_ignored(&config_trust_root(p)) || config_file::is_ignored(p))
})
.filter(|p| !config_path_is_ignored(p, false))
.collect();

Ok((paths, idiomatic_files))
Expand All @@ -1386,6 +1364,21 @@ fn is_default_config_dir_override_filtered(path: &Path) -> bool {
&& path.starts_with(&*env::MISE_DEFAULT_CONFIG_DIR)
}

fn config_dir_is_ignored(dir: &Path, include_ignored: bool) -> bool {
!include_ignored
&& env::MISE_IGNORED_CONFIG_PATHS
.iter()
.any(|p| dir.starts_with(p))
}

fn config_path_is_ignored(path: &Path, include_ignored: bool) -> bool {
if is_default_config_dir_override_filtered(path) {
return true;
}
!include_ignored
&& (config_file::is_ignored(&config_trust_root(path)) || config_file::is_ignored(path))
}

static GLOBAL_CONFIG_FILES: Lazy<Mutex<Option<IndexSet<PathBuf>>>> = Lazy::new(Default::default);
static SYSTEM_CONFIG_FILES: Lazy<Mutex<Option<IndexSet<PathBuf>>>> = Lazy::new(Default::default);

Expand Down Expand Up @@ -1476,30 +1469,37 @@ pub static ALL_TOML_CONFIG_FILES: Lazy<IndexSet<PathBuf>> = Lazy::new(|| {
.collect()
});

/// list of all non-global mise.tomls
pub fn local_toml_config_paths() -> Vec<&'static PathBuf> {
ALL_TOML_CONFIG_FILES
.iter()
.filter(|p| !is_global_config(p))
.collect()
}

/// The last (lowest precedence) local mise.toml or the path to where it should be written to.
/// This ensures commands write to mise.toml instead of mise.local.toml when both exist.
/// Note: local_toml_config_paths() returns files in highest-to-lowest precedence order,
/// so we use .last() to get the lowest precedence file.
/// The lowest-precedence TOML config in the nearest local config directory, or
/// the path where it should be written.
pub fn local_toml_config_path() -> PathBuf {
static CWD: Lazy<PathBuf> = Lazy::new(|| PathBuf::from("."));
local_toml_config_paths()
.into_iter()
.last()
.cloned()
.unwrap_or_else(|| {
dirs::CWD
.as_ref()
.unwrap_or(&CWD)
.join(&*env::MISE_DEFAULT_CONFIG_FILENAME)
})
local_toml_config_path_from_dir(dirs::CWD.as_ref().unwrap_or(&CWD))
}

/// The lowest-precedence TOML config in the nearest directory that has one.
///
/// This matches the write-target rule from the docs: choose the highest-precedence
/// directory first, then the lowest-precedence file inside that directory.
pub fn local_toml_config_path_from_dir(cwd: &Path) -> PathBuf {
if !Settings::no_config() {
for dir in all_dirs_from(cwd).unwrap_or_default() {
if config_dir_is_ignored(&dir, false) {
continue;
}
let files = TOML_CONFIG_FILENAMES
.iter()
.flat_map(|f| glob(&dir, f).unwrap_or_default())
.unique_by(|p| file::desymlink_path(p))
.filter(|p| !config_path_is_ignored(p, false))
.collect();
if let Some(cf) = first_config_file(&files)
&& !is_global_config(cf)
{
return cf.clone();
}
}
}
cwd.join(&*env::MISE_DEFAULT_CONFIG_FILENAME)
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
}

/// Options for resolving target config file path
Expand Down Expand Up @@ -1562,8 +1562,9 @@ pub fn resolve_target_config_path(opts: ConfigPathOptions) -> Result<PathBuf> {

// Default: determine based on current directory
if opts.prefer_toml {
// For mise set, prefer TOML and use local_toml_config_path logic
Ok(local_toml_config_path())
// For TOML-only commands, choose the nearest config directory and write
// to its lowest-precedence TOML file.
Ok(local_toml_config_path_from_dir(&cwd))
} else {
// For mise use, use existing config_file_from_dir logic which respects ASDF compat
Ok(config_file_from_dir(&cwd))
Expand Down
Loading