diff --git a/crates/memofs/src/in_memory_fs.rs b/crates/memofs/src/in_memory_fs.rs index 41006f1c..9ac1041d 100644 --- a/crates/memofs/src/in_memory_fs.rs +++ b/crates/memofs/src/in_memory_fs.rs @@ -235,30 +235,6 @@ impl VfsBackend for InMemoryFs { // TODO: We rely on Rojo to prepend cwd to any relative path before storing paths // in MemoFS. The current implementation will error if no prepended absolute path // is found. It really only normalizes paths within the provided path's context. - // Example: "/Users/username/project/../other/file.txt" -> - // "/Users/username/other/file.txt" - // Erroneous example: "/Users/../../other/file.txt" -> "/other/file.txt" - // This is not very robust. We should implement proper path normalization here or otherwise - // warn if we are missing context and can not fully canonicalize the path correctly. - fn canonicalize(&mut self, path: &Path) -> io::Result { - let mut normalized = PathBuf::new(); - for component in path.components() { - match component { - std::path::Component::ParentDir => { - normalized.pop(); - } - std::path::Component::CurDir => {} - _ => normalized.push(component), - } - } - - let inner = self.inner.lock().unwrap(); - match inner.entries.get(&normalized) { - Some(_) => Ok(normalized), - None => not_found(&normalized), - } - } - fn event_receiver(&self) -> crossbeam_channel::Receiver { let inner = self.inner.lock().unwrap(); diff --git a/crates/memofs/src/lib.rs b/crates/memofs/src/lib.rs index 2eaaba1c..3109d235 100644 --- a/crates/memofs/src/lib.rs +++ b/crates/memofs/src/lib.rs @@ -39,7 +39,6 @@ pub use std_backend::{CriticalErrorHandler, StdBackend, WatcherCriticalError}; /// the initial tree build to avoid stale data during live operation. pub struct PrefetchCache { pub files: HashMap>, - pub canonical: HashMap, /// `true` = file, `false` = directory. Paths not in the map fall through /// to the backend (e.g. init-file probes for paths that don't exist). pub is_file: HashMap, @@ -93,7 +92,6 @@ pub trait VfsBackend: sealed::Sealed + Send + 'static { fn metadata(&mut self, path: &Path) -> io::Result; fn remove_file(&mut self, path: &Path) -> io::Result<()>; fn remove_dir_all(&mut self, path: &Path) -> io::Result<()>; - fn canonicalize(&mut self, path: &Path) -> io::Result; fn event_receiver(&self) -> crossbeam_channel::Receiver; fn watch(&mut self, path: &Path) -> io::Result<()>; @@ -278,18 +276,6 @@ impl VfsInner { self.backend.metadata(path) } - fn canonicalize>(&mut self, path: P) -> io::Result { - let path = path.as_ref(); - - if let Some(cache) = &mut self.prefetch_cache { - if let Some(canonical) = cache.canonical.remove(path) { - return Ok(canonical); - } - } - - self.backend.canonicalize(path) - } - fn event_receiver(&self) -> crossbeam_channel::Receiver { self.backend.event_receiver() } @@ -369,9 +355,9 @@ impl Vfs { /// Load a prefetch cache for fast initial reads. /// - /// File reads and canonicalize calls will check the cache before hitting - /// the backend. Call [`clear_prefetch_cache`] after the initial snapshot - /// build to free memory and ensure live operations get fresh data. + /// File reads will check the cache before hitting the backend. Call + /// [`clear_prefetch_cache`] after the initial snapshot build to free + /// memory and ensure live operations get fresh data. pub fn set_prefetch_cache(&self, cache: PrefetchCache) { let mut inner = self.inner.lock().unwrap(); inner.prefetch_cache = Some(cache); @@ -533,19 +519,6 @@ impl Vfs { self.inner.lock().unwrap().metadata(path) } - /// Normalize a path via the underlying backend. - /// - /// Roughly equivalent to [`std::fs::canonicalize`][std::fs::canonicalize]. Relative paths are - /// resolved against the backend's current working directory (if applicable) and errors are - /// surfaced directly from the backend. - /// - /// [std::fs::canonicalize]: https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html - #[inline] - pub fn canonicalize>(&self, path: P) -> io::Result { - let path = path.as_ref(); - self.inner.lock().unwrap().canonicalize(path) - } - /// Retrieve a handle to the event receiver for this `Vfs`. #[inline] pub fn event_receiver(&self) -> crossbeam_channel::Receiver { @@ -673,13 +646,6 @@ impl VfsLock<'_> { self.inner.metadata(path) } - /// Normalize a path via the underlying backend. - #[inline] - pub fn normalize>(&mut self, path: P) -> io::Result { - let path = path.as_ref(); - self.inner.canonicalize(path) - } - /// Retrieve a handle to the event receiver for this `Vfs`. #[inline] pub fn event_receiver(&self) -> crossbeam_channel::Receiver { @@ -715,74 +681,12 @@ mod test { ); } - /// https://github.com/rojo-rbx/rojo/issues/1200 - #[test] - fn canonicalize_in_memory_success() { - let mut imfs = InMemoryFs::new(); - let contents = "Lorem ipsum dolor sit amet.".to_string(); - - imfs.load_snapshot("/test/file.txt", VfsSnapshot::file(contents.to_string())) - .unwrap(); - - let vfs = Vfs::new(imfs); - - assert_eq!( - vfs.canonicalize("/test/nested/../file.txt").unwrap(), - PathBuf::from("/test/file.txt") - ); - assert_eq!( - vfs.read_to_string(vfs.canonicalize("/test/nested/../file.txt").unwrap()) - .unwrap() - .to_string(), - contents.to_string() - ); - } - - #[test] - fn canonicalize_in_memory_missing_errors() { - let imfs = InMemoryFs::new(); - let vfs = Vfs::new(imfs); - - let err = vfs.canonicalize("test").unwrap_err(); - assert_eq!(err.kind(), io::ErrorKind::NotFound); - } - - #[test] - fn canonicalize_std_backend_success() { - let contents = "Lorem ipsum dolor sit amet.".to_string(); - let dir = tempfile::tempdir().unwrap(); - let file_path = dir.path().join("file.txt"); - fs_err::write(&file_path, contents.to_string()).unwrap(); - - let vfs = Vfs::new(StdBackend::new_for_testing()); - let canonicalized = vfs.canonicalize(&file_path).unwrap(); - assert_eq!(canonicalized, file_path.canonicalize().unwrap()); - assert_eq!( - vfs.read_to_string(&canonicalized).unwrap().to_string(), - contents.to_string() - ); - } - - #[test] - fn canonicalize_std_backend_missing_errors() { - let dir = tempfile::tempdir().unwrap(); - let file_path = dir.path().join("test"); - - let vfs = Vfs::new(StdBackend::new_for_testing()); - let err = vfs.canonicalize(&file_path).unwrap_err(); - assert_eq!(err.kind(), io::ErrorKind::NotFound); - } - - fn make_prefetch(files: Vec<(&str, &[u8])>, canonical: Vec<(&str, &str)>) -> PrefetchCache { + fn make_prefetch(files: Vec<(&str, &[u8])>) -> PrefetchCache { PrefetchCache { files: files .into_iter() .map(|(k, v)| (PathBuf::from(k), v.to_vec())) .collect(), - canonical: canonical - .into_iter() - .map(|(k, v)| (PathBuf::from(k), PathBuf::from(v))) - .collect(), is_file: HashMap::new(), children: HashMap::new(), } @@ -792,7 +696,7 @@ mod test { fn prefetch_cache_read_hit() { let imfs = InMemoryFs::new(); let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")], vec![])); + vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")])); assert_eq!(vfs.read("test").unwrap().as_slice(), b"cached"); } @@ -803,7 +707,7 @@ mod test { imfs.load_snapshot("test", VfsSnapshot::file("backend")) .unwrap(); let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")], vec![])); + vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")])); assert_eq!(vfs.read("test").unwrap().as_slice(), b"cached"); assert_eq!(vfs.read("test").unwrap().as_slice(), b"backend"); @@ -815,7 +719,7 @@ mod test { imfs.load_snapshot("other", VfsSnapshot::file("backend")) .unwrap(); let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")], vec![])); + vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")])); assert_eq!(vfs.read("other").unwrap().as_slice(), b"backend"); } @@ -824,7 +728,7 @@ mod test { fn prefetch_cache_read_to_string_hit() { let imfs = InMemoryFs::new(); let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![("test", b"hello")], vec![])); + vfs.set_prefetch_cache(make_prefetch(vec![("test", b"hello")])); assert_eq!(vfs.read_to_string("test").unwrap().as_str(), "hello"); } @@ -833,7 +737,7 @@ mod test { fn prefetch_cache_lf_normalized_hit() { let imfs = InMemoryFs::new(); let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![("test", b"line1\r\nline2\r\n")], vec![])); + vfs.set_prefetch_cache(make_prefetch(vec![("test", b"line1\r\nline2\r\n")])); assert_eq!( vfs.read_to_string_lf_normalized("test").unwrap().as_str(), @@ -841,39 +745,13 @@ mod test { ); } - #[test] - fn prefetch_cache_canonicalize_hit() { - let imfs = InMemoryFs::new(); - let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![], vec![("src/foo", "/abs/src/foo")])); - - assert_eq!( - vfs.canonicalize("src/foo").unwrap(), - PathBuf::from("/abs/src/foo") - ); - } - - #[test] - fn prefetch_cache_canonicalize_miss_falls_through() { - let mut imfs = InMemoryFs::new(); - imfs.load_snapshot("/real/file.txt", VfsSnapshot::file("x")) - .unwrap(); - let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![], vec![("other", "/abs/other")])); - - assert_eq!( - vfs.canonicalize("/real/file.txt").unwrap(), - PathBuf::from("/real/file.txt") - ); - } - #[test] fn prefetch_cache_clear_restores_backend() { let mut imfs = InMemoryFs::new(); imfs.load_snapshot("test", VfsSnapshot::file("backend")) .unwrap(); let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")], vec![])); + vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")])); vfs.clear_prefetch_cache(); assert_eq!(vfs.read("test").unwrap().as_slice(), b"backend"); @@ -914,45 +792,19 @@ mod test { fn prefetch_cache_read_to_string_invalid_utf8() { let imfs = InMemoryFs::new(); let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch( - vec![("test", &[0xFF, 0xFE, 0x00, 0x80])], - vec![], - )); + vfs.set_prefetch_cache(make_prefetch(vec![("test", &[0xFF, 0xFE, 0x00, 0x80])])); let err = vfs.read_to_string("test").unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidData); } - #[test] - fn prefetch_cache_canonicalize_depletion() { - let mut imfs = InMemoryFs::new(); - imfs.load_snapshot("/real/path", VfsSnapshot::file("x")) - .unwrap(); - let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch( - vec![], - vec![("/real/path", "/canonical/path")], - )); - - assert_eq!( - vfs.canonicalize("/real/path").unwrap(), - PathBuf::from("/canonical/path"), - "First call should hit cache" - ); - assert_eq!( - vfs.canonicalize("/real/path").unwrap(), - PathBuf::from("/real/path"), - "Second call should fall through to InMemoryFs backend" - ); - } - #[test] fn prefetch_cache_set_overwrite_previous() { let imfs = InMemoryFs::new(); let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![("a", b"first")], vec![])); - vfs.set_prefetch_cache(make_prefetch(vec![("a", b"second")], vec![])); + vfs.set_prefetch_cache(make_prefetch(vec![("a", b"first")])); + vfs.set_prefetch_cache(make_prefetch(vec![("a", b"second")])); assert_eq!(vfs.read("a").unwrap().as_slice(), b"second"); } @@ -1003,22 +855,17 @@ mod test { fn prefetch_cache_many_files_std_backend() { let dir = tempfile::tempdir().unwrap(); let mut cache_files = HashMap::new(); - let mut canonical = HashMap::new(); for i in 0..50 { let path = dir.path().join(format!("f{i}.txt")); let content = format!("data_{i}"); fs_err::write(&path, &content).unwrap(); cache_files.insert(path.clone(), content.into_bytes()); - if let Ok(c) = path.canonicalize() { - canonical.insert(path, c); - } } let vfs = Vfs::new(StdBackend::new_for_testing()); vfs.set_prefetch_cache(PrefetchCache { files: cache_files, - canonical, is_file: HashMap::new(), children: HashMap::new(), }); @@ -1046,7 +893,7 @@ mod test { imfs.load_snapshot("test", VfsSnapshot::file("original")) .unwrap(); let vfs = Vfs::new(imfs); - vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")], vec![])); + vfs.set_prefetch_cache(make_prefetch(vec![("test", b"cached")])); vfs.write("test", b"written").unwrap(); let data = vfs.read("test").unwrap(); diff --git a/crates/memofs/src/noop_backend.rs b/crates/memofs/src/noop_backend.rs index b1ee301d..98b86d1a 100644 --- a/crates/memofs/src/noop_backend.rs +++ b/crates/memofs/src/noop_backend.rs @@ -1,5 +1,5 @@ use std::io; -use std::path::{Path, PathBuf}; +use std::path::Path; use crate::{Metadata, ReadDir, VfsBackend, VfsEvent}; @@ -50,10 +50,6 @@ impl VfsBackend for NoopBackend { Err(io::Error::other("NoopBackend doesn't do anything")) } - fn canonicalize(&mut self, _path: &Path) -> io::Result { - Err(io::Error::other("NoopBackend doesn't do anything")) - } - fn event_receiver(&self) -> crossbeam_channel::Receiver { crossbeam_channel::never() } diff --git a/crates/memofs/src/std_backend.rs b/crates/memofs/src/std_backend.rs index 67bc1651..49f55539 100644 --- a/crates/memofs/src/std_backend.rs +++ b/crates/memofs/src/std_backend.rs @@ -324,10 +324,6 @@ impl VfsBackend for StdBackend { }) } - fn canonicalize(&mut self, path: &Path) -> io::Result { - fs_err::canonicalize(path) - } - fn event_receiver(&self) -> crossbeam_channel::Receiver { self.watcher_receiver.clone() } diff --git a/src/change_processor.rs b/src/change_processor.rs index 93aba74a..fbe41b20 100644 --- a/src/change_processor.rs +++ b/src/change_processor.rs @@ -99,11 +99,6 @@ impl ChangeProcessor { // (non-serve commands). never() blocks forever without selecting. let critical_error_receiver = critical_error_receiver.unwrap_or_else(crossbeam_channel::never); - // Canonicalize project_root so path comparisons work with the - // \\?\ prefix that std::fs::canonicalize adds on Windows. - let project_root = std::fs::canonicalize(&project_root).unwrap_or(project_root); - let project_file_path = - std::fs::canonicalize(&project_file_path).unwrap_or(project_file_path); let task = JobThreadContext { tree, vfs, @@ -306,23 +301,8 @@ impl JobThreadContext { None } - /// Canonicalize a path for use as a suppression map key. fn suppression_key(path: &Path) -> PathBuf { - if let Ok(canonical) = std::fs::canonicalize(path) { - canonical - } else if let Some(parent) = path.parent() { - if let Ok(canonical_parent) = std::fs::canonicalize(parent) { - if let Some(file_name) = path.file_name() { - canonical_parent.join(file_name) - } else { - path.to_path_buf() - } - } else { - path.to_path_buf() - } - } else { - path.to_path_buf() - } + path.to_path_buf() } /// Suppress the next Create/Write VFS event for the given path. @@ -686,49 +666,33 @@ impl JobThreadContext { _ => None, }; if let Some(ref path) = event_path { - let canonical = std::fs::canonicalize(path).ok(); let mut suppressed = self.suppressed_paths.lock().unwrap(); - // Determine which key matches: try canonical first (most likely - // to match what suppress_path stored), then fall back to raw. - let matched_key = canonical - .as_ref() - .filter(|c| suppressed.contains_key(c.as_path())) - .cloned() - .or_else(|| { - if suppressed.contains_key(path) { - Some(path.clone()) - } else { - None + let key = path.clone(); + if let Some(counts) = suppressed.get_mut(key.as_path()) { + let should_suppress = match &event { + VfsEvent::Remove(_) if counts.0 > 0 => { + counts.0 -= 1; + true } - }); - if let Some(key) = matched_key { - if let Some(counts) = suppressed.get_mut(&key) { - let should_suppress = match &event { - VfsEvent::Remove(_) if counts.0 > 0 => { - counts.0 -= 1; - true - } - VfsEvent::Create(_) | VfsEvent::Write(_) if counts.1 > 0 => { - counts.1 -= 1; - true - } - _ => false, - }; - if counts.0 == 0 && counts.1 == 0 { - suppressed.remove(&key); - } - if should_suppress { - drop(suppressed); - // Still commit so VFS stays consistent, but skip patching. - self.vfs - .commit_event(&event) - .expect("Error applying VFS change"); - log::info!( - "VFS event SUPPRESSED (API syncback echo): {}", - self.display_path(path) - ); - return Vec::new(); + VfsEvent::Create(_) | VfsEvent::Write(_) if counts.1 > 0 => { + counts.1 -= 1; + true } + _ => false, + }; + if counts.0 == 0 && counts.1 == 0 { + suppressed.remove(&key); + } + if should_suppress { + drop(suppressed); + self.vfs + .commit_event(&event) + .expect("Error applying VFS change"); + log::info!( + "VFS event SUPPRESSED (API syncback echo): {}", + self.display_path(path) + ); + return Vec::new(); } } } @@ -763,107 +727,81 @@ impl JobThreadContext { // of the tree. Calculate and apply all of these changes. let applied_patches = match event { VfsEvent::Create(path) | VfsEvent::Write(path) => { - match self.vfs.canonicalize(&path) { - Ok(canonical_path) => { - log::info!( - "VFS: canonicalize OK for {} -> {}", - self.display_path(&path), - self.display_path(&canonical_path) - ); - self.apply_patches(canonical_path) - } - Err(_) => { - // The path doesn't exist on disk. Two possible causes: - // - // 1. Phantom event from a rename we performed — macOS FSEvents - // can deliver a stale CREATE for the old path. If a pending - // suppression exists for this path, consume it and skip. - // - // 2. The file was deleted between the event firing and us - // processing it. Fall back to parent-directory canonicalize - // (same strategy as the Remove handler) so the tree can - // reconcile the disappearance. - let consumed = { - let key = Self::suppression_key(&path); - let mut suppressed = self.suppressed_paths.lock().unwrap(); - if let Some(counts) = suppressed.get_mut(&key) { - if counts.0 > 0 || counts.1 > 0 { - // Drain whichever counter is available - if counts.1 > 0 { - counts.1 -= 1; - } else { - counts.0 -= 1; - } - if counts.0 == 0 && counts.1 == 0 { - suppressed.remove(&key); - } - true + if path.exists() { + self.apply_patches(path) + } else { + // The path doesn't exist on disk. Two possible causes: + // + // 1. Phantom event from a rename we performed — macOS FSEvents + // can deliver a stale CREATE for the old path. If a pending + // suppression exists for this path, consume it and skip. + // + // 2. The file was deleted between the event firing and us + // processing it. Fall back to parent directory so the tree + // can reconcile the disappearance. + let consumed = { + let key = Self::suppression_key(&path); + let mut suppressed = self.suppressed_paths.lock().unwrap(); + if let Some(counts) = suppressed.get_mut(&key) { + if counts.0 > 0 || counts.1 > 0 { + if counts.1 > 0 { + counts.1 -= 1; } else { - false + counts.0 -= 1; } + if counts.0 == 0 && counts.1 == 0 { + suppressed.remove(&key); + } + true } else { false } - }; + } else { + false + } + }; - if consumed { + if consumed { + log::info!( + "VFS: phantom Create/Write for non-existent {} — \ + consumed pending suppression (likely stale rename event)", + self.display_path(&path) + ); + Vec::new() + } else { + let parent = path.parent().unwrap(); + let file_name = path.file_name().unwrap(); + if parent.exists() { + let resolved = parent.join(file_name); log::info!( - "VFS: phantom Create/Write for non-existent {} — \ - consumed pending suppression (likely stale rename event)", - self.display_path(&path) + "VFS: Create/Write for vanished file {} — \ + resolved via parent to {}", + self.display_path(&path), + self.display_path(&resolved) + ); + self.apply_patches(resolved) + } else { + log::info!( + "VFS: Skipping Create/Write for {} — \ + parent no longer exists", + self.display_path(&path), ); Vec::new() - } else { - // No suppression — the file genuinely vanished. Use the - // parent-directory fallback so the tree can reconcile. - let parent = path.parent().unwrap(); - let file_name = path.file_name().unwrap(); - match self.vfs.canonicalize(parent) { - Ok(parent_normalized) => { - let resolved = parent_normalized.join(file_name); - log::info!( - "VFS: Create/Write for vanished file {} — \ - resolved via parent to {}", - self.display_path(&path), - self.display_path(&resolved) - ); - self.apply_patches(resolved) - } - Err(err) => { - log::info!( - "VFS: Skipping Create/Write for {} — \ - parent no longer exists: {}", - self.display_path(&path), - err - ); - Vec::new() - } - } } } } } VfsEvent::Remove(path) => { - // MemoFS does not track parent removals yet, so we can canonicalize - // the parent path safely and then append the removed path's file name. - // However, if the parent was also deleted (e.g., when deleting a directory - // tree), canonicalize will fail - in that case, just skip this event. let parent = path.parent().unwrap(); - let file_name = path.file_name().unwrap(); - match self.vfs.canonicalize(parent) { - Ok(parent_normalized) => { - let resolved = parent_normalized.join(file_name); - log::info!("VFS: Remove resolved to {}", self.display_path(&resolved)); - self.apply_patches(resolved) - } - Err(err) => { - log::info!( - "VFS: Skipping remove event for {} — parent no longer exists: {}", - self.display_path(&path), - err - ); - Vec::new() - } + if parent.exists() { + log::info!("VFS: Remove for {}", self.display_path(&path)); + self.apply_patches(path) + } else { + log::info!( + "VFS: Skipping remove event for {} — parent no longer exists", + self.display_path(&path), + ); + Vec::new() } } _ => { diff --git a/src/cli/sourcemap.rs b/src/cli/sourcemap.rs index 51f418db..45db803c 100644 --- a/src/cli/sourcemap.rs +++ b/src/cli/sourcemap.rs @@ -263,8 +263,7 @@ pub(crate) fn write_sourcemap( ) -> anyhow::Result<()> { let tree = session.tree(); - let canonical_project_dir = std::fs::canonicalize(session.root_dir()) - .unwrap_or_else(|_| session.root_dir().to_path_buf()); + let canonical_project_dir = session.root_dir().to_path_buf(); let root_node = recurse_create_node( &tree, @@ -306,8 +305,7 @@ pub(crate) fn write_sourcemap_from_syncback( project_dir: &Path, output: &Path, ) -> anyhow::Result<()> { - let canonical_project_dir = - std::fs::canonicalize(project_dir).unwrap_or_else(|_| project_dir.to_path_buf()); + let canonical_project_dir = project_dir.to_path_buf(); let root_node = recurse_create_node_from_dom(dom, dom.root_ref(), instance_paths, &canonical_project_dir); diff --git a/src/serve_session.rs b/src/serve_session.rs index aaaa85d8..1ef4628d 100644 --- a/src/serve_session.rs +++ b/src/serve_session.rs @@ -130,7 +130,7 @@ pub struct ServeSession { } /// Collect all filesystem paths reachable from the project tree's `$path` -/// entries, then read file contents and compute canonical paths in parallel. +/// entries, then read file contents in parallel. fn prefetch_project_files(project: &Project) -> io::Result { use rayon::prelude::*; use std::collections::HashMap; @@ -153,7 +153,6 @@ fn prefetch_project_files(project: &Project) -> io::Result { if roots.is_empty() { return Ok(PrefetchCache { files: HashMap::new(), - canonical: HashMap::new(), is_file: HashMap::new(), children: HashMap::new(), }); @@ -208,24 +207,11 @@ fn prefetch_project_files(project: &Project) -> io::Result { .collect(); let read_elapsed = read_start.elapsed(); - let canon_start = Instant::now(); - - let canonical_data: Vec<_> = entries - .par_iter() - .filter_map(|e| { - let path = e.path().to_path_buf(); - std::fs::canonicalize(&path).ok().map(|c| (path, c)) - }) - .collect(); - - let canon_elapsed = canon_start.elapsed(); log::debug!( - "Prefetch I/O: read {} files in {:.1?}, canonicalize {} paths in {:.1?}", + "Prefetch I/O: read {} files in {:.1?}", file_data.len(), read_elapsed, - canonical_data.len(), - canon_elapsed, ); let mut is_file_map: HashMap = HashMap::new(); @@ -257,7 +243,6 @@ fn prefetch_project_files(project: &Project) -> io::Result { Ok(PrefetchCache { files: file_data.into_iter().collect::>(), - canonical: canonical_data.into_iter().collect::>(), is_file: is_file_map, children: children_map, }) @@ -287,9 +272,8 @@ impl ServeSession { match prefetch_project_files(&root_project) { Ok(cache) => { log::debug!( - "Prefetch total: {} files + {} canonical paths in {:.1?}", + "Prefetch total: {} files in {:.1?}", cache.files.len(), - cache.canonical.len(), prefetch_start.elapsed() ); vfs.set_prefetch_cache(cache); diff --git a/src/snapshot_middleware/csv.rs b/src/snapshot_middleware/csv.rs index 2a5e0c3b..e872396c 100644 --- a/src/snapshot_middleware/csv.rs +++ b/src/snapshot_middleware/csv.rs @@ -42,7 +42,7 @@ pub fn snapshot_csv( .metadata( InstanceMetadata::new() .instigating_source(path) - .relevant_paths(vec![vfs.canonicalize(path)?]), + .relevant_paths(vec![path.to_path_buf()]), ); AdjacentMetadata::read_and_apply_all(vfs, path, name, &mut snapshot)?; diff --git a/src/snapshot_middleware/dir.rs b/src/snapshot_middleware/dir.rs index a19fbdd5..0ed2e106 100644 --- a/src/snapshot_middleware/dir.rs +++ b/src/snapshot_middleware/dir.rs @@ -85,7 +85,7 @@ pub fn snapshot_dir_no_meta( children }; - let normalized_path = vfs.canonicalize(path)?; + let normalized_path = path.to_path_buf(); let relevant_paths = vec![ normalized_path.clone(), // TODO: We shouldn't need to know about Lua existing in this diff --git a/src/snapshot_middleware/json.rs b/src/snapshot_middleware/json.rs index ce08b7f9..4044f26a 100644 --- a/src/snapshot_middleware/json.rs +++ b/src/snapshot_middleware/json.rs @@ -32,7 +32,7 @@ pub fn snapshot_json( .metadata( InstanceMetadata::new() .instigating_source(path) - .relevant_paths(vec![vfs.canonicalize(path)?]) + .relevant_paths(vec![path.to_path_buf()]) .context(context), ); diff --git a/src/snapshot_middleware/json_model.rs b/src/snapshot_middleware/json_model.rs index 064d1ff6..51ee8e62 100644 --- a/src/snapshot_middleware/json_model.rs +++ b/src/snapshot_middleware/json_model.rs @@ -53,7 +53,7 @@ pub fn snapshot_json_model( snapshot.metadata = snapshot .metadata .instigating_source(path) - .relevant_paths(vec![vfs.canonicalize(path)?]) + .relevant_paths(vec![path.to_path_buf()]) .context(context) .specified_id(id) .schema(schema) diff --git a/src/snapshot_middleware/lua.rs b/src/snapshot_middleware/lua.rs index 0d79d8af..4427fcef 100644 --- a/src/snapshot_middleware/lua.rs +++ b/src/snapshot_middleware/lua.rs @@ -72,7 +72,7 @@ pub fn snapshot_lua( .metadata( InstanceMetadata::new() .instigating_source(path) - .relevant_paths(vec![vfs.canonicalize(path)?]) + .relevant_paths(vec![path.to_path_buf()]) .context(context), ); diff --git a/src/snapshot_middleware/project.rs b/src/snapshot_middleware/project.rs index 3df51f95..2f5964b6 100644 --- a/src/snapshot_middleware/project.rs +++ b/src/snapshot_middleware/project.rs @@ -147,10 +147,7 @@ pub fn snapshot_project( // We SHOULD NOT mark the project file as a relevant path for any // nodes that aren't roots. They'll be updated as part of the project // file being updated. - snapshot - .metadata - .relevant_paths - .push(vfs.canonicalize(path)?); + snapshot.metadata.relevant_paths.push(path.to_path_buf()); Ok(Some(snapshot)) } diff --git a/src/snapshot_middleware/rbxm.rs b/src/snapshot_middleware/rbxm.rs index e02e0995..38b820b0 100644 --- a/src/snapshot_middleware/rbxm.rs +++ b/src/snapshot_middleware/rbxm.rs @@ -30,7 +30,7 @@ pub fn snapshot_rbxm( .metadata( InstanceMetadata::new() .instigating_source(path) - .relevant_paths(vec![vfs.canonicalize(path)?]) + .relevant_paths(vec![path.to_path_buf()]) .context(context), ); diff --git a/src/snapshot_middleware/rbxmx.rs b/src/snapshot_middleware/rbxmx.rs index eb8a313a..c2b6576d 100644 --- a/src/snapshot_middleware/rbxmx.rs +++ b/src/snapshot_middleware/rbxmx.rs @@ -33,7 +33,7 @@ pub fn snapshot_rbxmx( .metadata( InstanceMetadata::new() .instigating_source(path) - .relevant_paths(vec![vfs.canonicalize(path)?]) + .relevant_paths(vec![path.to_path_buf()]) .context(context), ); diff --git a/src/snapshot_middleware/toml.rs b/src/snapshot_middleware/toml.rs index ab1ef412..8a9d4788 100644 --- a/src/snapshot_middleware/toml.rs +++ b/src/snapshot_middleware/toml.rs @@ -33,7 +33,7 @@ pub fn snapshot_toml( .metadata( InstanceMetadata::new() .instigating_source(path) - .relevant_paths(vec![vfs.canonicalize(path)?]) + .relevant_paths(vec![path.to_path_buf()]) .context(context), ); diff --git a/src/snapshot_middleware/txt.rs b/src/snapshot_middleware/txt.rs index 17e65145..00699ef7 100644 --- a/src/snapshot_middleware/txt.rs +++ b/src/snapshot_middleware/txt.rs @@ -28,7 +28,7 @@ pub fn snapshot_txt( .metadata( InstanceMetadata::new() .instigating_source(path) - .relevant_paths(vec![vfs.canonicalize(path)?]) + .relevant_paths(vec![path.to_path_buf()]) .context(context), ); diff --git a/src/snapshot_middleware/yaml.rs b/src/snapshot_middleware/yaml.rs index 044993ae..5cff3814 100644 --- a/src/snapshot_middleware/yaml.rs +++ b/src/snapshot_middleware/yaml.rs @@ -37,7 +37,7 @@ pub fn snapshot_yaml( .metadata( InstanceMetadata::new() .instigating_source(path) - .relevant_paths(vec![vfs.canonicalize(path)?]) + .relevant_paths(vec![path.to_path_buf()]) .context(context), ); diff --git a/src/syncback/mod.rs b/src/syncback/mod.rs index 85feb3a9..9b04685a 100644 --- a/src/syncback/mod.rs +++ b/src/syncback/mod.rs @@ -254,15 +254,6 @@ pub fn syncback_loop_with_stats( let project_path = project.folder_location(); - fn strip_unc_prefix(p: PathBuf) -> PathBuf { - let path_str = p.to_string_lossy(); - if path_str.starts_with(r"\\?\") || path_str.starts_with("//?/") { - PathBuf::from(&path_str[4..]) - } else { - p - } - } - let phase_timer = std::time::Instant::now(); let existing_paths: HashSet = if !incremental { let mut paths = HashSet::new(); @@ -407,10 +398,7 @@ pub fn syncback_loop_with_stats( continue; } } - let canonical_dir = std::fs::canonicalize(dir) - .map(strip_unc_prefix) - .unwrap_or_else(|_| dir.clone()); - for entry in walkdir::WalkDir::new(&canonical_dir) + for entry in walkdir::WalkDir::new(dir) .follow_links(true) .into_iter() .filter_entry(|e| { @@ -661,14 +649,10 @@ pub fn syncback_loop_with_stats( if !incremental && !existing_paths.is_empty() { log::debug!("Clean mode: checking for orphaned files to remove"); - let canonical_project_path = strip_unc_prefix( - std::fs::canonicalize(project_path).unwrap_or_else(|_| project_path.to_path_buf()), - ); - let added_paths: HashSet = fs_snapshot .added_paths() .into_iter() - .map(|p| strip_unc_prefix(canonical_project_path.join(p))) + .map(|p| project_path.join(p)) .collect(); let mut added_dir_prefixes: HashSet = HashSet::new(); @@ -681,10 +665,7 @@ pub fn syncback_loop_with_stats( } } - let project_file = strip_unc_prefix( - std::fs::canonicalize(&project.file_location) - .unwrap_or_else(|_| project.file_location.clone()), - ); + let project_file = project.file_location.clone(); // Collect ALL paths explicitly referenced via $path in the project. // These paths should NOT be removed during orphan cleanup because they @@ -706,12 +687,9 @@ pub fn syncback_loop_with_stats( ) { if let Some(ref path_node) = node.path { let resolved = base_path.join(path_node.path()); - let canonical = std::fs::canonicalize(&resolved) - .map(strip_unc_prefix) - .unwrap_or_else(|_| resolved.clone()); - protected.insert(canonical.clone()); + protected.insert(resolved.clone()); if !instance_path.is_empty() { - mappings.push((canonical, instance_path.to_string())); + mappings.push((resolved, instance_path.to_string())); } } for (name, child) in &node.children { @@ -881,9 +859,7 @@ pub fn syncback_loop_with_stats( } current_ancestor = Some(old_path); - let relative_path = old_path - .strip_prefix(&canonical_project_path) - .unwrap_or(old_path); + let relative_path = old_path.strip_prefix(project_path).unwrap_or(old_path); log::debug!("Removing orphaned path: {}", relative_path.display()); if old_path.is_dir() { diff --git a/src/web/api.rs b/src/web/api.rs index 3b9a04d8..ba539bda 100644 --- a/src/web/api.rs +++ b/src/web/api.rs @@ -358,23 +358,8 @@ impl ApiService { } } - /// Canonicalize a path for use as a suppression map key. fn suppression_key(path: &Path) -> PathBuf { - if let Ok(canonical) = std::fs::canonicalize(path) { - canonical - } else if let Some(parent) = path.parent() { - if let Ok(canonical_parent) = std::fs::canonicalize(parent) { - if let Some(file_name) = path.file_name() { - canonical_parent.join(file_name) - } else { - path.to_path_buf() - } - } else { - path.to_path_buf() - } - } else { - path.to_path_buf() - } + path.to_path_buf() } /// Suppress the next Create/Write VFS event for the given path. diff --git a/tests/tests/build.rs b/tests/tests/build.rs index 58464cf0..9b61d116 100644 --- a/tests/tests/build.rs +++ b/tests/tests/build.rs @@ -241,22 +241,17 @@ fn parallel_snapshot_with_prefetch_cache() { use std::collections::HashMap; let mut files = HashMap::new(); - let mut canonical = HashMap::new(); for entry in walkdir::WalkDir::new(root).follow_links(true) { let entry = entry.unwrap(); let path = entry.path().to_path_buf(); if entry.file_type().is_file() { files.insert(path.clone(), fs::read(&path).unwrap()); } - if let Ok(c) = fs::canonicalize(&path) { - canonical.insert(path, c); - } } let vfs_cached = memofs::Vfs::new_default(); vfs_cached.set_prefetch_cache(memofs::PrefetchCache { files, - canonical, is_file: std::collections::HashMap::new(), children: std::collections::HashMap::new(), }); @@ -336,7 +331,6 @@ fn overlapping_path_roots_no_duplicate_children() { use std::collections::HashMap; let mut files = HashMap::new(); - let mut canonical = HashMap::new(); let mut is_file = HashMap::new(); let mut children_map: HashMap> = HashMap::new(); @@ -347,9 +341,6 @@ fn overlapping_path_roots_no_duplicate_children() { if entry.file_type().is_file() { files.insert(path.clone(), fs::read(&path).unwrap()); } - if let Ok(c) = fs::canonicalize(&path) { - canonical.insert(path.clone(), c); - } if entry.depth() > 0 { if let Some(parent) = entry.path().parent() { children_map @@ -366,7 +357,6 @@ fn overlapping_path_roots_no_duplicate_children() { let vfs = memofs::Vfs::new_default(); vfs.set_prefetch_cache(memofs::PrefetchCache { files, - canonical, is_file, children: children_map, });