Detailed changes
@@ -492,7 +492,7 @@ mod custom_path_matcher {
pub fn new(globs: &[String]) -> Result<Self, globset::Error> {
let globs = globs
.iter()
- .map(|glob| Glob::new(&SanitizedPath::from(glob).to_glob_string()))
+ .map(|glob| Glob::new(&SanitizedPath::new(glob).to_glob_string()))
.collect::<Result<Vec<_>, _>>()?;
let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect();
let sources_with_trailing_slash = globs
@@ -495,7 +495,8 @@ impl Fs for RealFs {
};
// todo(windows)
// When new version of `windows-rs` release, make this operation `async`
- let path = SanitizedPath::from(path.canonicalize()?);
+ let path = path.canonicalize()?;
+ let path = SanitizedPath::new(&path);
let path_string = path.to_string();
let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(path_string))?.get()?;
file.DeleteAsync(StorageDeleteOption::Default)?.get()?;
@@ -522,7 +523,8 @@ impl Fs for RealFs {
// todo(windows)
// When new version of `windows-rs` release, make this operation `async`
- let path = SanitizedPath::from(path.canonicalize()?);
+ let path = path.canonicalize()?;
+ let path = SanitizedPath::new(&path);
let path_string = path.to_string();
let folder = StorageFolder::GetFolderFromPathAsync(&HSTRING::from(path_string))?.get()?;
folder.DeleteAsync(StorageDeleteOption::Default)?.get()?;
@@ -783,7 +785,7 @@ impl Fs for RealFs {
{
target = parent.join(target);
if let Ok(canonical) = self.canonicalize(&target).await {
- target = SanitizedPath::from(canonical).as_path().to_path_buf();
+ target = SanitizedPath::new(&canonical).as_path().to_path_buf();
}
}
watcher.add(&target).ok();
@@ -42,7 +42,7 @@ impl Drop for FsWatcher {
impl Watcher for FsWatcher {
fn add(&self, path: &std::path::Path) -> anyhow::Result<()> {
- let root_path = SanitizedPath::from(path);
+ let root_path = SanitizedPath::new_arc(path);
let tx = self.tx.clone();
let pending_paths = self.pending_path_events.clone();
@@ -70,7 +70,7 @@ impl Watcher for FsWatcher {
.paths
.iter()
.filter_map(|event_path| {
- let event_path = SanitizedPath::from(event_path);
+ let event_path = SanitizedPath::new(event_path);
event_path.starts_with(&root_path).then(|| PathEvent {
path: event_path.as_path().to_path_buf(),
kind,
@@ -851,7 +851,7 @@ fn file_save_dialog(
if !directory.to_string_lossy().is_empty()
&& let Some(full_path) = directory.canonicalize().log_err()
{
- let full_path = SanitizedPath::from(full_path);
+ let full_path = SanitizedPath::new(&full_path);
let full_path_string = full_path.to_string();
let path_item: IShellItem =
unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_string), None)? };
@@ -63,7 +63,7 @@ pub fn set_custom_data_dir(dir: &str) -> &'static PathBuf {
let abs_path = path
.canonicalize()
.expect("failed to canonicalize custom data directory's path to an absolute path");
- path = PathBuf::from(util::paths::SanitizedPath::from(abs_path))
+ path = util::paths::SanitizedPath::new(&abs_path).into()
}
std::fs::create_dir_all(&path).expect("failed to create custom data directory");
path
@@ -62,7 +62,7 @@ use std::{
};
use sum_tree::{Edit, SumTree, TreeSet};
use text::{Bias, BufferId};
-use util::{ResultExt, debug_panic, post_inc};
+use util::{ResultExt, debug_panic, paths::SanitizedPath, post_inc};
use worktree::{
File, PathChange, PathKey, PathProgress, PathSummary, PathTarget, ProjectEntryId,
UpdatedGitRepositoriesSet, UpdatedGitRepository, Worktree,
@@ -3234,6 +3234,7 @@ impl Repository {
let git_store = self.git_store.upgrade()?;
let worktree_store = git_store.read(cx).worktree_store.read(cx);
let abs_path = self.snapshot.work_directory_abs_path.join(&path.0);
+ let abs_path = SanitizedPath::new(&abs_path);
let (worktree, relative_path) = worktree_store.find_worktree(abs_path, cx)?;
Some(ProjectPath {
worktree_id: worktree.read(cx).id(),
@@ -3186,7 +3186,7 @@ impl LocalLspStore {
} else {
let (path, pattern) = match &watcher.glob_pattern {
lsp::GlobPattern::String(s) => {
- let watcher_path = SanitizedPath::from(s);
+ let watcher_path = SanitizedPath::new(s);
let path = glob_literal_prefix(watcher_path.as_path());
let pattern = watcher_path
.as_path()
@@ -3278,7 +3278,7 @@ impl LocalLspStore {
let worktree_root_path = tree.abs_path();
match &watcher.glob_pattern {
lsp::GlobPattern::String(s) => {
- let watcher_path = SanitizedPath::from(s);
+ let watcher_path = SanitizedPath::new(s);
let relative = watcher_path
.as_path()
.strip_prefix(&worktree_root_path)
@@ -2061,13 +2061,12 @@ impl Project {
exclude_sub_dirs: bool,
cx: &App,
) -> Option<bool> {
- let sanitized_path = SanitizedPath::from(path);
- let path = sanitized_path.as_path();
+ let path = SanitizedPath::new(path).as_path();
self.worktrees(cx)
.filter_map(|worktree| {
let worktree = worktree.read(cx);
let abs_path = worktree.as_local()?.abs_path();
- let contains = path == abs_path
+ let contains = path == abs_path.as_ref()
|| (path.starts_with(abs_path) && (!exclude_sub_dirs || !metadata.is_dir));
contains.then(|| worktree.is_visible())
})
@@ -61,7 +61,7 @@ pub struct WorktreeStore {
worktrees_reordered: bool,
#[allow(clippy::type_complexity)]
loading_worktrees:
- HashMap<SanitizedPath, Shared<Task<Result<Entity<Worktree>, Arc<anyhow::Error>>>>>,
+ HashMap<Arc<SanitizedPath>, Shared<Task<Result<Entity<Worktree>, Arc<anyhow::Error>>>>>,
state: WorktreeStoreState,
}
@@ -153,10 +153,10 @@ impl WorktreeStore {
pub fn find_worktree(
&self,
- abs_path: impl Into<SanitizedPath>,
+ abs_path: impl AsRef<Path>,
cx: &App,
) -> Option<(Entity<Worktree>, PathBuf)> {
- let abs_path: SanitizedPath = abs_path.into();
+ let abs_path = SanitizedPath::new(&abs_path);
for tree in self.worktrees() {
if let Ok(relative_path) = abs_path.as_path().strip_prefix(tree.read(cx).abs_path()) {
return Some((tree.clone(), relative_path.into()));
@@ -212,11 +212,11 @@ impl WorktreeStore {
pub fn create_worktree(
&mut self,
- abs_path: impl Into<SanitizedPath>,
+ abs_path: impl AsRef<Path>,
visible: bool,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Worktree>>> {
- let abs_path: SanitizedPath = abs_path.into();
+ let abs_path: Arc<SanitizedPath> = SanitizedPath::new_arc(&abs_path);
if !self.loading_worktrees.contains_key(&abs_path) {
let task = match &self.state {
WorktreeStoreState::Remote {
@@ -227,8 +227,7 @@ impl WorktreeStore {
if upstream_client.is_via_collab() {
Task::ready(Err(Arc::new(anyhow!("cannot create worktrees via collab"))))
} else {
- let abs_path =
- RemotePathBuf::new(abs_path.as_path().to_path_buf(), *path_style);
+ let abs_path = RemotePathBuf::new(abs_path.to_path_buf(), *path_style);
self.create_ssh_worktree(upstream_client.clone(), abs_path, visible, cx)
}
}
@@ -321,15 +320,21 @@ impl WorktreeStore {
fn create_local_worktree(
&mut self,
fs: Arc<dyn Fs>,
- abs_path: impl Into<SanitizedPath>,
+ abs_path: Arc<SanitizedPath>,
visible: bool,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Worktree>, Arc<anyhow::Error>>> {
let next_entry_id = self.next_entry_id.clone();
- let path: SanitizedPath = abs_path.into();
cx.spawn(async move |this, cx| {
- let worktree = Worktree::local(path.clone(), visible, fs, next_entry_id, cx).await;
+ let worktree = Worktree::local(
+ SanitizedPath::cast_arc(abs_path.clone()),
+ visible,
+ fs,
+ next_entry_id,
+ cx,
+ )
+ .await;
let worktree = worktree?;
@@ -337,7 +342,7 @@ impl WorktreeStore {
if visible {
cx.update(|cx| {
- cx.add_recent_document(path.as_path());
+ cx.add_recent_document(abs_path.as_path());
})
.log_err();
}
@@ -902,7 +902,7 @@ impl SshRemoteConnection {
// On Windows, the binding needs to be set to the canonical path
#[cfg(target_os = "windows")]
let src =
- SanitizedPath::from(smol::fs::canonicalize("./target").await?).to_glob_string();
+ SanitizedPath::new(&smol::fs::canonicalize("./target").await?).to_glob_string();
#[cfg(not(target_os = "windows"))]
let src = "./target";
run_cmd(
@@ -3,6 +3,7 @@ use regex::Regex;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
+use std::mem;
use std::path::StripPrefixError;
use std::sync::{Arc, OnceLock};
use std::{
@@ -99,21 +100,86 @@ impl<T: AsRef<Path>> PathExt for T {
}
}
-/// Due to the issue of UNC paths on Windows, which can cause bugs in various parts of Zed, introducing this `SanitizedPath`
-/// leverages Rust's type system to ensure that all paths entering Zed are always "sanitized" by removing the `\\\\?\\` prefix.
-/// On non-Windows operating systems, this struct is effectively a no-op.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct SanitizedPath(pub Arc<Path>);
+/// In memory, this is identical to `Path`. On non-Windows conversions to this type are no-ops. On
+/// windows, these conversions sanitize UNC paths by removing the `\\\\?\\` prefix.
+#[derive(Eq, PartialEq, Hash, Ord, PartialOrd)]
+#[repr(transparent)]
+pub struct SanitizedPath(Path);
impl SanitizedPath {
- pub fn starts_with(&self, prefix: &SanitizedPath) -> bool {
+ pub fn new<T: AsRef<Path> + ?Sized>(path: &T) -> &Self {
+ #[cfg(not(target_os = "windows"))]
+ return Self::unchecked_new(path.as_ref());
+
+ #[cfg(target_os = "windows")]
+ return Self::unchecked_new(dunce::simplified(path.as_ref()));
+ }
+
+ pub fn unchecked_new<T: AsRef<Path> + ?Sized>(path: &T) -> &Self {
+ // safe because `Path` and `SanitizedPath` have the same repr and Drop impl
+ unsafe { mem::transmute::<&Path, &Self>(path.as_ref()) }
+ }
+
+ pub fn from_arc(path: Arc<Path>) -> Arc<Self> {
+ // safe because `Path` and `SanitizedPath` have the same repr and Drop impl
+ #[cfg(not(target_os = "windows"))]
+ return unsafe { mem::transmute::<Arc<Path>, Arc<Self>>(path) };
+
+ // TODO: could avoid allocating here if dunce::simplified results in the same path
+ #[cfg(target_os = "windows")]
+ return Self::new(&path).into();
+ }
+
+ pub fn new_arc<T: AsRef<Path> + ?Sized>(path: &T) -> Arc<Self> {
+ Self::new(path).into()
+ }
+
+ pub fn cast_arc(path: Arc<Self>) -> Arc<Path> {
+ // safe because `Path` and `SanitizedPath` have the same repr and Drop impl
+ unsafe { mem::transmute::<Arc<Self>, Arc<Path>>(path) }
+ }
+
+ pub fn cast_arc_ref(path: &Arc<Self>) -> &Arc<Path> {
+ // safe because `Path` and `SanitizedPath` have the same repr and Drop impl
+ unsafe { mem::transmute::<&Arc<Self>, &Arc<Path>>(path) }
+ }
+
+ pub fn starts_with(&self, prefix: &Self) -> bool {
self.0.starts_with(&prefix.0)
}
- pub fn as_path(&self) -> &Arc<Path> {
+ pub fn as_path(&self) -> &Path {
&self.0
}
+ pub fn file_name(&self) -> Option<&std::ffi::OsStr> {
+ self.0.file_name()
+ }
+
+ pub fn extension(&self) -> Option<&std::ffi::OsStr> {
+ self.0.extension()
+ }
+
+ pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf {
+ self.0.join(path)
+ }
+
+ pub fn parent(&self) -> Option<&Self> {
+ self.0.parent().map(Self::unchecked_new)
+ }
+
+ pub fn strip_prefix(&self, base: &Self) -> Result<&Path, StripPrefixError> {
+ self.0.strip_prefix(base.as_path())
+ }
+
+ pub fn to_str(&self) -> Option<&str> {
+ self.0.to_str()
+ }
+
+ pub fn to_path_buf(&self) -> PathBuf {
+ self.0.to_path_buf()
+ }
+
pub fn to_glob_string(&self) -> String {
#[cfg(target_os = "windows")]
{
@@ -124,13 +190,11 @@ impl SanitizedPath {
self.0.to_string_lossy().to_string()
}
}
+}
- pub fn join(&self, path: &Self) -> Self {
- self.0.join(&path.0).into()
- }
-
- pub fn strip_prefix(&self, base: &Self) -> Result<&Path, StripPrefixError> {
- self.0.strip_prefix(base.as_path())
+impl std::fmt::Debug for SanitizedPath {
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Debug::fmt(&self.0, formatter)
}
}
@@ -140,29 +204,23 @@ impl Display for SanitizedPath {
}
}
-impl From<SanitizedPath> for Arc<Path> {
- fn from(sanitized_path: SanitizedPath) -> Self {
- sanitized_path.0
+impl From<&SanitizedPath> for Arc<SanitizedPath> {
+ fn from(sanitized_path: &SanitizedPath) -> Self {
+ let path: Arc<Path> = sanitized_path.0.into();
+ // safe because `Path` and `SanitizedPath` have the same repr and Drop impl
+ unsafe { mem::transmute(path) }
}
}
-impl From<SanitizedPath> for PathBuf {
- fn from(sanitized_path: SanitizedPath) -> Self {
- sanitized_path.0.as_ref().into()
+impl From<&SanitizedPath> for PathBuf {
+ fn from(sanitized_path: &SanitizedPath) -> Self {
+ sanitized_path.as_path().into()
}
}
-impl<T: AsRef<Path>> From<T> for SanitizedPath {
- #[cfg(not(target_os = "windows"))]
- fn from(path: T) -> Self {
- let path = path.as_ref();
- SanitizedPath(path.into())
- }
-
- #[cfg(target_os = "windows")]
- fn from(path: T) -> Self {
- let path = path.as_ref();
- SanitizedPath(dunce::simplified(path).into())
+impl AsRef<Path> for SanitizedPath {
+ fn as_ref(&self) -> &Path {
+ &self.0
}
}
@@ -1195,14 +1253,14 @@ mod tests {
#[cfg(target_os = "windows")]
fn test_sanitized_path() {
let path = Path::new("C:\\Users\\someone\\test_file.rs");
- let sanitized_path = SanitizedPath::from(path);
+ let sanitized_path = SanitizedPath::new(path);
assert_eq!(
sanitized_path.to_string(),
"C:\\Users\\someone\\test_file.rs"
);
let path = Path::new("\\\\?\\C:\\Users\\someone\\test_file.rs");
- let sanitized_path = SanitizedPath::from(path);
+ let sanitized_path = SanitizedPath::new(path);
assert_eq!(
sanitized_path.to_string(),
"C:\\Users\\someone\\test_file.rs"
@@ -26,7 +26,7 @@ impl PathList {
let mut indexed_paths: Vec<(usize, PathBuf)> = paths
.iter()
.enumerate()
- .map(|(ix, path)| (ix, SanitizedPath::from(path).into()))
+ .map(|(ix, path)| (ix, SanitizedPath::new(path).into()))
.collect();
indexed_paths.sort_by(|(_, a), (_, b)| a.cmp(b));
let order = indexed_paths.iter().map(|e| e.0).collect::<Vec<_>>().into();
@@ -2576,7 +2576,7 @@ impl Workspace {
};
let this = this.clone();
- let abs_path: Arc<Path> = SanitizedPath::from(abs_path.clone()).into();
+ let abs_path: Arc<Path> = SanitizedPath::new(&abs_path).as_path().into();
let fs = fs.clone();
let pane = pane.clone();
let task = cx.spawn(async move |cx| {
@@ -158,7 +158,7 @@ pub struct RemoteWorktree {
#[derive(Clone)]
pub struct Snapshot {
id: WorktreeId,
- abs_path: SanitizedPath,
+ abs_path: Arc<SanitizedPath>,
root_name: String,
root_char_bag: CharBag,
entries_by_path: SumTree<Entry>,
@@ -457,7 +457,7 @@ enum ScanState {
scanning: bool,
},
RootUpdated {
- new_path: Option<SanitizedPath>,
+ new_path: Option<Arc<SanitizedPath>>,
},
}
@@ -763,8 +763,8 @@ impl Worktree {
pub fn abs_path(&self) -> Arc<Path> {
match self {
- Worktree::Local(worktree) => worktree.abs_path.clone().into(),
- Worktree::Remote(worktree) => worktree.abs_path.clone().into(),
+ Worktree::Local(worktree) => SanitizedPath::cast_arc(worktree.abs_path.clone()),
+ Worktree::Remote(worktree) => SanitizedPath::cast_arc(worktree.abs_path.clone()),
}
}
@@ -1813,7 +1813,7 @@ impl LocalWorktree {
// Otherwise, the FS watcher would do it on the `RootUpdated` event,
// but with a noticeable delay, so we handle it proactively.
local.update_abs_path_and_refresh(
- Some(SanitizedPath::from(abs_path.clone())),
+ Some(SanitizedPath::new_arc(&abs_path)),
cx,
);
Task::ready(Ok(this.root_entry().cloned()))
@@ -2090,7 +2090,7 @@ impl LocalWorktree {
fn update_abs_path_and_refresh(
&mut self,
- new_path: Option<SanitizedPath>,
+ new_path: Option<Arc<SanitizedPath>>,
cx: &Context<Worktree>,
) {
if let Some(new_path) = new_path {
@@ -2340,7 +2340,7 @@ impl Snapshot {
pub fn new(id: u64, root_name: String, abs_path: Arc<Path>) -> Self {
Snapshot {
id: WorktreeId::from_usize(id as usize),
- abs_path: abs_path.into(),
+ abs_path: SanitizedPath::from_arc(abs_path),
root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(),
root_name,
always_included_entries: Default::default(),
@@ -2368,7 +2368,7 @@ impl Snapshot {
//
// This is definitely a bug, but it's not clear if we should handle it here or not.
pub fn abs_path(&self) -> &Arc<Path> {
- self.abs_path.as_path()
+ SanitizedPath::cast_arc_ref(&self.abs_path)
}
fn build_initial_update(&self, project_id: u64, worktree_id: u64) -> proto::UpdateWorktree {
@@ -2464,7 +2464,7 @@ impl Snapshot {
Some(removed_entry.path)
}
- fn update_abs_path(&mut self, abs_path: SanitizedPath, root_name: String) {
+ fn update_abs_path(&mut self, abs_path: Arc<SanitizedPath>, root_name: String) {
self.abs_path = abs_path;
if root_name != self.root_name {
self.root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
@@ -2483,7 +2483,7 @@ impl Snapshot {
update.removed_entries.len()
);
self.update_abs_path(
- SanitizedPath::from(PathBuf::from_proto(update.abs_path)),
+ SanitizedPath::new_arc(&PathBuf::from_proto(update.abs_path)),
update.root_name,
);
@@ -3849,7 +3849,11 @@ impl BackgroundScanner {
root_entry.is_ignored = true;
state.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref());
}
- state.enqueue_scan_dir(root_abs_path.into(), &root_entry, &scan_job_tx);
+ state.enqueue_scan_dir(
+ SanitizedPath::cast_arc(root_abs_path),
+ &root_entry,
+ &scan_job_tx,
+ );
}
};
@@ -3930,8 +3934,9 @@ impl BackgroundScanner {
self.forcibly_load_paths(&request.relative_paths).await;
let root_path = self.state.lock().snapshot.abs_path.clone();
- let root_canonical_path = match self.fs.canonicalize(root_path.as_path()).await {
- Ok(path) => SanitizedPath::from(path),
+ let root_canonical_path = self.fs.canonicalize(root_path.as_path()).await;
+ let root_canonical_path = match &root_canonical_path {
+ Ok(path) => SanitizedPath::new(path),
Err(err) => {
log::error!("failed to canonicalize root path {root_path:?}: {err}");
return true;
@@ -3959,8 +3964,8 @@ impl BackgroundScanner {
}
self.reload_entries_for_paths(
- root_path,
- root_canonical_path,
+ &root_path,
+ &root_canonical_path,
&request.relative_paths,
abs_paths,
None,
@@ -3972,8 +3977,9 @@ impl BackgroundScanner {
async fn process_events(&self, mut abs_paths: Vec<PathBuf>) {
let root_path = self.state.lock().snapshot.abs_path.clone();
- let root_canonical_path = match self.fs.canonicalize(root_path.as_path()).await {
- Ok(path) => SanitizedPath::from(path),
+ let root_canonical_path = self.fs.canonicalize(root_path.as_path()).await;
+ let root_canonical_path = match &root_canonical_path {
+ Ok(path) => SanitizedPath::new(path),
Err(err) => {
let new_path = self
.state
@@ -3982,7 +3988,7 @@ impl BackgroundScanner {
.root_file_handle
.clone()
.and_then(|handle| handle.current_path(&self.fs).log_err())
- .map(SanitizedPath::from)
+ .map(|path| SanitizedPath::new_arc(&path))
.filter(|new_path| *new_path != root_path);
if let Some(new_path) = new_path.as_ref() {
@@ -4011,7 +4017,7 @@ impl BackgroundScanner {
abs_paths.sort_unstable();
abs_paths.dedup_by(|a, b| a.starts_with(b));
abs_paths.retain(|abs_path| {
- let abs_path = SanitizedPath::from(abs_path);
+ let abs_path = &SanitizedPath::new(abs_path);
let snapshot = &self.state.lock().snapshot;
{
@@ -4054,7 +4060,7 @@ impl BackgroundScanner {
return false;
};
- if abs_path.0.file_name() == Some(*GITIGNORE) {
+ if abs_path.file_name() == Some(*GITIGNORE) {
for (_, repo) in snapshot.git_repositories.iter().filter(|(_, repo)| repo.directory_contains(&relative_path)) {
if !dot_git_abs_paths.iter().any(|dot_git_abs_path| dot_git_abs_path == repo.common_dir_abs_path.as_ref()) {
dot_git_abs_paths.push(repo.common_dir_abs_path.to_path_buf());
@@ -4093,8 +4099,8 @@ impl BackgroundScanner {
let (scan_job_tx, scan_job_rx) = channel::unbounded();
log::debug!("received fs events {:?}", relative_paths);
self.reload_entries_for_paths(
- root_path,
- root_canonical_path,
+ &root_path,
+ &root_canonical_path,
&relative_paths,
abs_paths,
Some(scan_job_tx.clone()),
@@ -4441,8 +4447,8 @@ impl BackgroundScanner {
/// All list arguments should be sorted before calling this function
async fn reload_entries_for_paths(
&self,
- root_abs_path: SanitizedPath,
- root_canonical_path: SanitizedPath,
+ root_abs_path: &SanitizedPath,
+ root_canonical_path: &SanitizedPath,
relative_paths: &[Arc<Path>],
abs_paths: Vec<PathBuf>,
scan_queue_tx: Option<Sender<ScanJob>>,
@@ -4470,7 +4476,7 @@ impl BackgroundScanner {
}
}
- anyhow::Ok(Some((metadata, SanitizedPath::from(canonical_path))))
+ anyhow::Ok(Some((metadata, SanitizedPath::new_arc(&canonical_path))))
} else {
Ok(None)
}