Keep watching directories recreated at a removed path (AI-assisted)

This commit is contained in:
spoukyii 2026-10-16 02:30:15 -07:00
parent a36a199835
commit f54ade744d
4 changed files with 50 additions and 5 deletions

1
AI-canary.txt Normal file
View file

@ -0,0 +1 @@
This pull request is AI generated

View file

@ -32,8 +32,10 @@ Making a new release? Simply add the new header with the version and date undern
## Unreleased
* LocalizationTables no longer report as changed when nothing changes. [(#1333)]
* Fixed live sync on Linux (`rojo serve` and the `--watch` modes) missing every change inside a directory that was removed or moved aside and then recreated at the same path within another synced directory. ([#1341])
[#1333]: https://github.com/rojo-rbx/rojo/pull/1333
[#1341]: https://github.com/rojo-rbx/rojo/pull/1341
## [7.7.1] (October 1st, 2026)

View file

@ -1,6 +1,9 @@
# memofs Changelog
## Unreleased Changes
* Fixed `StdBackend` on Linux dropping the watches on a directory recreated at a path whose removal was committed afterwards, which stopped events for everything inside it. [#1341]
[#1341]: https://github.com/rojo-rbx/rojo/pull/1341
# 0.4.0 (2026-07-01)
* Added `Vfs::canonicalize`. [#1201]

View file

@ -122,15 +122,54 @@ impl VfsBackend for StdBackend {
{
Ok(())
} else {
// This recursive watch covers any watch we already hold below
// `path`. Release those, so that every path in `watches` is the
// only watch covering it and `unwatch` can trust that. Release them
// first: on inotify this watch shares their per-directory entries,
// and unwatching them afterwards would delete its coverage too.
let covered: Vec<PathBuf> = self
.watches
.iter()
.filter(|watched| watched.starts_with(path))
.cloned()
.collect();
for watched in &covered {
self.watches.remove(watched);
let _ = self.watcher.unwatch(watched);
}
if let Err(err) = self.watcher.watch(path, RecursiveMode::Recursive) {
// Put back what we released, so a failed watch loses nothing.
// inotify keeps whatever part of the walk succeeded, so drop
// that first to free its watches.
let _ = self.watcher.unwatch(path);
for watched in covered {
if self
.watcher
.watch(&watched, RecursiveMode::Recursive)
.is_ok()
{
self.watches.insert(watched);
}
}
return Err(io::Error::other(err));
}
self.watches.insert(path.to_path_buf());
self.watcher
.watch(path, RecursiveMode::Recursive)
.map_err(io::Error::other)
Ok(())
}
}
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
self.watches.remove(path);
self.watcher.unwatch(path).map_err(io::Error::other)
// Only stop a watch we started. Any other path is covered by an
// ancestor's recursive watch, for which inotify keeps an entry per
// directory, keyed by path. By the time a removal is committed, that
// entry may belong to a new directory created at the same path, and
// unwatching it would stop events for that directory and everything
// under it.
if self.watches.remove(path) {
self.watcher.unwatch(path).map_err(io::Error::other)
} else {
Ok(())
}
}
}