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 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])
|
* 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])
|
* 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
|
[7.7.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.7.1
|
||||||
[#1290]: https://github.com/rojo-rbx/rojo/pull/1290
|
[#1290]: https://github.com/rojo-rbx/rojo/pull/1290
|
||||||
[#1297]: https://github.com/rojo-rbx/rojo/pull/1297
|
[#1297]: https://github.com/rojo-rbx/rojo/pull/1297
|
||||||
|
[#1314]: https://github.com/rojo-rbx/rojo/issues/1314
|
||||||
|
|
||||||
## [7.7.0] (July 1st, 2026)
|
## [7.7.0] (July 1st, 2026)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use crossbeam_channel::{select, Receiver, RecvError, Sender};
|
||||||
use jod_thread::JoinHandle;
|
use jod_thread::JoinHandle;
|
||||||
use memofs::{IoResultExt, Vfs, VfsEvent};
|
use memofs::{IoResultExt, Vfs, VfsEvent};
|
||||||
use rbx_dom_weak::types::{Ref, Variant};
|
use rbx_dom_weak::types::{Ref, Variant};
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs, io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -113,6 +113,31 @@ struct JobThreadContext {
|
||||||
message_queue: Arc<MessageQueue<AppliedPatchSet>>,
|
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 {
|
impl JobThreadContext {
|
||||||
/// Computes and applies patches to the DOM for a given file path.
|
/// 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
|
// For a given VFS event, we might have many changes to different parts
|
||||||
// of the tree. Calculate and apply all of these changes.
|
// of the tree. Calculate and apply all of these changes.
|
||||||
let applied_patches = match event {
|
let applied_patches = match event {
|
||||||
VfsEvent::Create(path) | VfsEvent::Write(path) => {
|
VfsEvent::Create(path) | VfsEvent::Write(path) | VfsEvent::Remove(path) => {
|
||||||
self.apply_patches(self.vfs.canonicalize(&path).unwrap())
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::warn!("Unhandled VFS event: {:?}", event);
|
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)
|
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