mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-20 17:41:37 +00:00
Merge 5e3963e1ee into 7e60406373
This commit is contained in:
commit
f8d4431577
3 changed files with 97 additions and 12 deletions
1
AI-canary.txt
Normal file
1
AI-canary.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
This pull request is AI generated
|
||||
|
|
@ -41,10 +41,12 @@ Making a new release? Simply add the new header with the version and date undern
|
|||
* Fixed `rojo serve` silently stopping syncing file changes on Windows when the served project path was a verbatim (`\\?\`) path, because tree paths and file-watcher event paths were canonicalized to different forms. ([#1290])
|
||||
* Fixed `rojo sourcemap --absolute` emitting verbatim (`\\?\`) paths on Windows, which broke require types in luau-lsp. ([#1290])
|
||||
* The plugin now disables the `Check for Updates` setting if you block access to `api.github.com`. ([#1297])
|
||||
* Fixed `rojo serve` crashing when queued filesystem events refer to paths whose ancestors have already been removed. ([#1314])
|
||||
|
||||
[7.7.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.7.1
|
||||
[#1290]: https://github.com/rojo-rbx/rojo/pull/1290
|
||||
[#1297]: https://github.com/rojo-rbx/rojo/pull/1297
|
||||
[#1314]: https://github.com/rojo-rbx/rojo/issues/1314
|
||||
|
||||
## [7.7.0] (July 1st, 2026)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ use crossbeam_channel::{select, Receiver, RecvError, Sender};
|
|||
use jod_thread::JoinHandle;
|
||||
use memofs::{IoResultExt, Vfs, VfsEvent};
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
fs,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
|
|
@ -113,6 +113,31 @@ struct JobThreadContext {
|
|||
message_queue: Arc<MessageQueue<AppliedPatchSet>>,
|
||||
}
|
||||
|
||||
/// Canonicalizes as much of an event path as still exists.
|
||||
///
|
||||
/// Filesystem events can become stale while they wait to be processed. For
|
||||
/// example, recursively removing a directory can remove a child's parent
|
||||
/// before the child's event is handled. Canonicalizing the nearest existing
|
||||
/// ancestor preserves the normalized prefix used by the tree while allowing
|
||||
/// the missing suffix to still drive reconciliation.
|
||||
fn canonicalize_event_path(vfs: &Vfs, path: &Path) -> io::Result<PathBuf> {
|
||||
let mut ancestor = path;
|
||||
|
||||
loop {
|
||||
match vfs.canonicalize(ancestor) {
|
||||
Ok(canonical_ancestor) => {
|
||||
let missing_suffix = path.strip_prefix(ancestor).map_err(io::Error::other)?;
|
||||
return Ok(canonical_ancestor.join(missing_suffix));
|
||||
}
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => match ancestor.parent() {
|
||||
Some(parent) => ancestor = parent,
|
||||
None => return Err(err),
|
||||
},
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JobThreadContext {
|
||||
/// Computes and applies patches to the DOM for a given file path.
|
||||
///
|
||||
|
|
@ -168,16 +193,18 @@ impl JobThreadContext {
|
|||
// For a given VFS event, we might have many changes to different parts
|
||||
// of the tree. Calculate and apply all of these changes.
|
||||
let applied_patches = match event {
|
||||
VfsEvent::Create(path) | VfsEvent::Write(path) => {
|
||||
self.apply_patches(self.vfs.canonicalize(&path).unwrap())
|
||||
}
|
||||
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.
|
||||
let parent = path.parent().unwrap();
|
||||
let file_name = path.file_name().unwrap();
|
||||
let parent_normalized = self.vfs.canonicalize(parent).unwrap();
|
||||
self.apply_patches(parent_normalized.join(file_name))
|
||||
VfsEvent::Create(path) | VfsEvent::Write(path) | VfsEvent::Remove(path) => {
|
||||
match canonicalize_event_path(&self.vfs, &path) {
|
||||
Ok(path) => self.apply_patches(path),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Could not canonicalize filesystem event path {}: {}",
|
||||
path.display(),
|
||||
err
|
||||
);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Unhandled VFS event: {:?}", event);
|
||||
|
|
@ -383,3 +410,58 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
|
|||
|
||||
Some(applied_patch_set)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use memofs::{InMemoryFs, StdBackend, VfsSnapshot};
|
||||
|
||||
use crate::snapshot::InstanceSnapshot;
|
||||
|
||||
fn stale_event_context() -> JobThreadContext {
|
||||
let mut backend = InMemoryFs::new();
|
||||
backend
|
||||
.load_snapshot("/project", VfsSnapshot::empty_dir())
|
||||
.unwrap();
|
||||
|
||||
JobThreadContext {
|
||||
tree: Arc::new(Mutex::new(RojoTree::new(InstanceSnapshot::new()))),
|
||||
vfs: Arc::new(Vfs::new(backend)),
|
||||
message_queue: Arc::new(MessageQueue::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_remove_event_does_not_panic_when_parent_was_removed() {
|
||||
let context = stale_event_context();
|
||||
let path = PathBuf::from("/project/out/server/deep/file.luau");
|
||||
|
||||
assert_eq!(canonicalize_event_path(&context.vfs, &path).unwrap(), path);
|
||||
|
||||
context.handle_vfs_event(VfsEvent::Remove(path));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_write_event_does_not_panic_when_path_was_removed() {
|
||||
let context = stale_event_context();
|
||||
|
||||
context.handle_vfs_event(VfsEvent::Write("/project/out/server/deep/file.luau".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_event_path_canonicalizes_with_std_backend() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let missing_suffix = Path::new("out/server/deep/file.luau");
|
||||
let missing_path = dir.path().join(missing_suffix);
|
||||
let expected_path = dunce::canonicalize(dir.path())
|
||||
.unwrap()
|
||||
.join(missing_suffix);
|
||||
let vfs = Vfs::new(StdBackend::new().unwrap());
|
||||
|
||||
assert_eq!(
|
||||
canonicalize_event_path(&vfs, &missing_path).unwrap(),
|
||||
expected_path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue