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
17 changes: 7 additions & 10 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ use crate::cmd::CmdLineRunner;
use crate::config::config_file::config_root;
use crate::config::{Config, Settings};
use crate::duration::parse_into_timestamp;
use crate::file::{display_path, remove_all_with_progress, remove_all_with_warning};
use crate::file::{
canonicalize_cached, display_path, remove_all_with_progress, remove_all_with_warning,
};
use crate::install_before::resolve_before_date;
use crate::install_context::InstallContext;
use crate::lockfile::{PlatformInfo, ProvenanceType};
Expand Down Expand Up @@ -1346,21 +1348,16 @@ pub trait Backend: Debug + Send + Sync {
};
// Canonicalize to resolve any ".." components before checking.
// If target doesn't exist (canonicalize fails), don't skip - treat as needing install
let Ok(target) = target.canonicalize() else {
return None;
};
let target = canonicalize_cached(&target)?;
// Canonicalize INSTALLS too for consistent comparison (handles symlinked data dirs)
let installs = dirs::INSTALLS
.canonicalize()
.unwrap_or(dirs::INSTALLS.to_path_buf());
let installs =
canonicalize_cached(&dirs::INSTALLS).unwrap_or(dirs::INSTALLS.to_path_buf());
if target.starts_with(&installs) {
return Some(path);
}
// Also check shared install directories
for shared_dir in env::shared_install_dirs() {
let shared = shared_dir
.canonicalize()
.unwrap_or(shared_dir.to_path_buf());
let shared = canonicalize_cached(&shared_dir).unwrap_or(shared_dir.to_path_buf());
if target.starts_with(&shared) {
return Some(path);
}
Expand Down
16 changes: 6 additions & 10 deletions src/cli/activate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};

use crate::config::Settings;
use crate::env::PATH_KEY;
use crate::file::touch_dir;
use crate::file::{canonicalize_cached, canonicalize_or_self, touch_dir};
use crate::path_env::PathEnv;
use crate::shell::{ActivateOptions, ActivatePrelude, Shell, ShellType, get_shell};
use crate::toolset::env_cache::CachedEnv;
Expand Down Expand Up @@ -193,12 +193,10 @@ fn remove_shims() -> std::io::Result<Option<ActivatePrelude>> {
return Ok(None);
}

let shims = dirs::SHIMS
.canonicalize()
.unwrap_or(dirs::SHIMS.to_path_buf());
let shims = canonicalize_or_self(&dirs::SHIMS);
if env::PATH
.iter()
.filter_map(|p| p.canonicalize().ok())
.filter_map(|p| canonicalize_cached(p))
.contains(&shims)
{
let path_env = PathEnv::from_iter(env::PATH.clone());
Expand All @@ -211,17 +209,15 @@ fn remove_shims() -> std::io::Result<Option<ActivatePrelude>> {
}

fn is_dir_in_path(dir: &Path) -> bool {
let dir = dir.canonicalize().unwrap_or(dir.to_path_buf());
let dir = canonicalize_or_self(dir);
env::PATH
.clone()
.into_iter()
.any(|p| p.canonicalize().unwrap_or(p) == dir)
.any(|p| canonicalize_or_self(&p) == dir)
}

fn is_dir_not_in_nix(dir: &Path) -> bool {
!dir.canonicalize()
.unwrap_or(dir.to_path_buf())
.starts_with("/nix/")
!canonicalize_or_self(dir).starts_with("/nix/")
}

static AFTER_LONG_HELP: &str = color_print::cstr!(
Expand Down
6 changes: 3 additions & 3 deletions src/cli/doctor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::cli::version;
use crate::cli::version::VERSION;
use crate::config::{Config, IGNORED_CONFIG_FILES, Settings};
use crate::env::PATH_KEY;
use crate::file::display_path;
use crate::file::{canonicalize_cached, canonicalize_or_self, display_path};
use crate::git::Git;
use crate::plugins::PluginType;
use crate::plugins::core::CORE_PLUGINS;
Expand Down Expand Up @@ -525,13 +525,13 @@ impl Doctor {
return;
}

let resolve = |p: &PathBuf| p.canonicalize().unwrap_or_else(|_| p.clone());
let resolve = |p: &PathBuf| canonicalize_or_self(p);

// Resolve all mise-managed paths for comparison
let mise_paths_resolved: HashSet<PathBuf> = mise_paths.iter().map(resolve).collect();

// Also exclude the mise binary's own directory
let mise_bin_parent = env::MISE_BIN.parent().and_then(|p| p.canonicalize().ok());
let mise_bin_parent = env::MISE_BIN.parent().and_then(canonicalize_cached);

// Find the index of the first mise-managed path in the current PATH
// Note: mise_bin_parent is intentionally excluded here — it's a directory like
Expand Down
12 changes: 6 additions & 6 deletions src/cli/hook_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::direnv::DirenvDiff;
use crate::env::{__MISE_DIFF, PATH_KEY, TERM_WIDTH};
use crate::env::{join_paths, split_paths};
use crate::env_diff::{EnvDiff, EnvDiffOperation, EnvMap};
use crate::file::display_rel_path;
use crate::file::{canonicalize_cached, display_rel_path};
use crate::hook_env::{PREV_SESSION, WatchFilePattern};
use crate::shell::{ShellType, get_shell};
use crate::toolset::Toolset;
Expand Down Expand Up @@ -324,12 +324,12 @@ impl HookEnv {
// Use canonicalized paths for comparison to handle symlinks, relative paths,
// and other path variants that refer to the same filesystem location.
let post_canonical: HashSet<PathBuf> =
post.iter().filter_map(|p| p.canonicalize().ok()).collect();
post.iter().filter_map(|p| canonicalize_cached(p)).collect();
let user_additions_set: HashSet<_> = pre.iter().chain(post_user.iter()).collect();
let user_additions_canonical: HashSet<PathBuf> = pre
.iter()
.chain(post_user.iter())
.filter_map(|p| p.canonicalize().ok())
.filter_map(|p| canonicalize_cached(p))
.collect();

let tool_paths_filtered: Vec<PathBuf> = tool_paths
Expand All @@ -343,7 +343,7 @@ impl HookEnv {
if post.contains(p) {
return false;
}
if let Ok(canonical) = p.canonicalize()
if let Some(canonical) = canonicalize_cached(p)
&& post_canonical.contains(&canonical)
{
return false;
Expand All @@ -353,7 +353,7 @@ impl HookEnv {
if user_additions_set.contains(p) {
return false;
}
if let Ok(canonical) = p.canonicalize()
if let Some(canonical) = canonicalize_cached(p)
&& user_additions_canonical.contains(&canonical)
{
return false;
Expand All @@ -375,7 +375,7 @@ impl HookEnv {
if user_additions_set.contains(p) {
return false;
}
if let Ok(canonical) = p.canonicalize()
if let Some(canonical) = canonicalize_cached(p)
&& user_additions_canonical.contains(&canonical)
{
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ impl Settings {
.iter()
.filter(|p| !p.to_string_lossy().is_empty())
.map(file::replace_path)
.filter_map(|p| p.canonicalize().ok())
.filter_map(|p| file::canonicalize_cached(&p))
}

pub fn global_tools_file(&self) -> PathBuf {
Expand Down
28 changes: 28 additions & 0 deletions src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,34 @@ pub fn which_non_pristine<P: AsRef<Path>>(name: P) -> Option<PathBuf> {
_which(name, &env::PATH_NON_PRISTINE)
}

/// Canonicalize a path and cache successful resolutions for the current process.
///
/// Use this for repeated comparisons against stable roots or PATH entries. Failed
/// canonicalizations are not cached because many callers handle paths that may be
/// created later in the same process.
pub fn canonicalize_cached(path: &Path) -> Option<PathBuf> {
static CACHE: Lazy<Mutex<HashMap<PathBuf, PathBuf>>> = Lazy::new(Default::default);

if !path.is_absolute() {
return path.canonicalize().ok();
}
if let Some(path) = CACHE.lock().unwrap().get(path).cloned() {
return Some(path);
}
let canonicalized = path.canonicalize().ok()?;
CACHE
.lock()
.unwrap()
.insert(path.to_path_buf(), canonicalized.clone());
Some(canonicalized)
}

/// Canonicalize a path using the process cache, falling back to the original
/// path when canonicalization fails.
pub fn canonicalize_or_self(path: &Path) -> PathBuf {
canonicalize_cached(path).unwrap_or_else(|| path.to_path_buf())
}

/// Build a PATH value with mise shims filtered out, suitable for passing to
/// subprocesses via `.env("PATH", ...)`. Prevents infinite recursion when a
/// subprocess (e.g. `gh auth token`, `git credential fill`) resolves to a
Expand Down
17 changes: 7 additions & 10 deletions src/shims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,22 @@ async fn which_shim(config: &mut Arc<Config>, bin_name: &str) -> Result<PathBuf>
}
}
// fallback for "system"
let mise_bin = fs::canonicalize(&*env::MISE_BIN).unwrap_or_else(|_| env::MISE_BIN.clone());
let user_shims = fs::canonicalize(*dirs::SHIMS).unwrap_or_default();
let mise_bin = file::canonicalize_or_self(&env::MISE_BIN);
let user_shims = file::canonicalize_cached(&dirs::SHIMS);
let sys_shims = {
let p = env::MISE_SYSTEM_DATA_DIR.join("shims");
if p.exists() {
fs::canonicalize(&p).unwrap_or(p)
} else {
PathBuf::new()
}
file::canonicalize_cached(&p)
};
for path in &*env::PATH {
let canon_path = fs::canonicalize(path).unwrap_or_default();
if canon_path == user_shims || canon_path == sys_shims {
if let Some(canon_path) = file::canonicalize_cached(path)
&& (user_shims.as_ref() == Some(&canon_path) || sys_shims.as_ref() == Some(&canon_path))
{
continue;
}
let bin = path.join(bin_name);
if bin.exists() {
// Skip if this binary is a mise shim (symlink pointing to the mise binary)
if fs::canonicalize(&bin).unwrap_or_default() == mise_bin {
if file::canonicalize_cached(&bin).is_some_and(|bin| bin == mise_bin) {
continue;
}
trace!("shim[{bin_name}] SYSTEM {bin}", bin = display_path(&bin));
Expand Down
3 changes: 2 additions & 1 deletion src/task/task_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::file::canonicalize_or_self;
use crate::task::Task;
use std::path::{Path, PathBuf};

Expand All @@ -11,5 +12,5 @@ pub fn task_needs_permit(task: &Task) -> bool {
/// Canonicalize a path for use as cache key
/// Falls back to original path if canonicalization fails
pub fn canonicalize_path(path: &Path) -> PathBuf {
path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
canonicalize_or_self(path)
}
Loading