From fc8a6e9b9cf7c80cda3c58473a3728ed5cbbfaae Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 1 Mar 2026 18:42:41 -0600 Subject: [PATCH] Remove canonicalization functionality from VFS and related components - Eliminated the `canonicalize` method from the `VfsBackend` trait and its implementations in `InMemoryFs`, `NoopBackend`, and `StdBackend`, simplifying the interface and reducing complexity. - Updated various parts of the codebase to remove references to canonicalization, including tests and prefetch cache handling, ensuring consistency across the project. - Adjusted logging and comments to reflect the removal of path normalization logic, enhancing clarity and maintainability. These changes aim to streamline the VFS implementation by focusing on core functionalities without the overhead of path canonicalization. --- crates/memofs/src/in_memory_fs.rs | 24 --- crates/memofs/src/lib.rs | 181 ++------------------ crates/memofs/src/noop_backend.rs | 6 +- crates/memofs/src/std_backend.rs | 4 - src/change_processor.rs | 232 ++++++++++---------------- src/cli/sourcemap.rs | 6 +- src/serve_session.rs | 22 +-- src/snapshot_middleware/csv.rs | 2 +- src/snapshot_middleware/dir.rs | 2 +- src/snapshot_middleware/json.rs | 2 +- src/snapshot_middleware/json_model.rs | 2 +- src/snapshot_middleware/lua.rs | 2 +- src/snapshot_middleware/project.rs | 5 +- src/snapshot_middleware/rbxm.rs | 2 +- src/snapshot_middleware/rbxmx.rs | 2 +- src/snapshot_middleware/toml.rs | 2 +- src/snapshot_middleware/txt.rs | 2 +- src/snapshot_middleware/yaml.rs | 2 +- src/syncback/mod.rs | 36 +--- src/web/api.rs | 17 +- tests/tests/build.rs | 10 -- 21 files changed, 123 insertions(+), 440 deletions(-) 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, });