feat: add backend flags for rename events

This commit is contained in:
CJ van den Berg 2026-03-29 14:59:14 +02:00
parent 99dec3f689
commit f6158c8240
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
7 changed files with 64 additions and 5 deletions

View file

@ -7,6 +7,8 @@ const ObjectType = types.ObjectType;
pub const watches_recursively = true; // FSEventStreamCreate watches the entire subtree
pub const detects_file_modifications = true;
pub const emits_close_events = false;
pub const emits_rename_for_files = true;
pub const emits_rename_for_dirs = true;
handler: *Handler,
stream: ?*anyopaque, // FSEventStreamRef

View file

@ -36,6 +36,8 @@ pub fn Create(comptime variant: InterfaceType) type {
pub const watches_recursively = false;
pub const detects_file_modifications = true;
pub const emits_close_events = true;
pub const emits_rename_for_files = true;
pub const emits_rename_for_dirs = true;
pub const polling = variant == .polling;
const WatchEntry = struct { path: []u8, is_dir: bool };

View file

@ -7,6 +7,8 @@ const ObjectType = types.ObjectType;
pub const watches_recursively = false;
pub const detects_file_modifications = true;
pub const emits_close_events = false;
pub const emits_rename_for_files = false;
pub const emits_rename_for_dirs = true;
handler: *Handler,
kq: std.posix.fd_t,

View file

@ -7,6 +7,8 @@ const ObjectType = types.ObjectType;
pub const watches_recursively = false;
pub const detects_file_modifications = false;
pub const emits_close_events = false;
pub const emits_rename_for_files = false;
pub const emits_rename_for_dirs = true;
pub const WatchEntry = struct { fd: std.posix.fd_t, is_file: bool };
handler: *Handler,

View file

@ -7,6 +7,8 @@ const ObjectType = types.ObjectType;
pub const watches_recursively = true; // ReadDirectoryChangesW with bWatchSubtree=1
pub const detects_file_modifications = true;
pub const emits_close_events = false;
pub const emits_rename_for_files = true;
pub const emits_rename_for_dirs = true;
const windows = std.os.windows;

View file

@ -115,6 +115,8 @@ pub fn Create(comptime variant: Variant) type {
/// modifications.
pub const detects_file_modifications = Backend.detects_file_modifications;
pub const emits_close_events = Backend.emits_close_events;
pub const emits_rename_for_files = Backend.emits_rename_for_files;
pub const emits_rename_for_dirs = Backend.emits_rename_for_dirs;
/// Create a new watcher.
///

View file

@ -394,15 +394,56 @@ fn testRenameFile(comptime Watcher: type, allocator: std.mem.Allocator) !void {
try std.fs.renameAbsolute(src_path, dst_path);
try drainEvents(Watcher, &watcher);
if (builtin.os.tag == .linux) {
try std.testing.expect(th.hasRename(src_path, dst_path));
if (comptime Watcher.emits_rename_for_files) {
// INotify delivers a paired atomic rename callback; FSEvents/Windows
// deliver individual .renamed change events per path.
const has_rename = th.hasRename(src_path, dst_path) or
th.hasChange(src_path, .renamed, .file);
try std.testing.expect(has_rename);
} else {
const has_old = th.hasChange(src_path, .renamed, .file) or th.hasChange(src_path, .deleted, .file);
const has_new = th.hasChange(dst_path, .renamed, .file) or th.hasChange(dst_path, .created, .file);
try std.testing.expect(has_old or has_new);
// KQueue/KQueueDir: file rename appears as delete + create.
try std.testing.expect(th.hasChange(src_path, .deleted, .file));
try std.testing.expect(th.hasChange(dst_path, .created, .file));
}
}
fn testRenameDir(comptime Watcher: type, allocator: std.mem.Allocator) !void {
if (comptime !Watcher.emits_rename_for_dirs) return error.SkipZigTest;
const TH = MakeTestHandler(Watcher);
const tmp = try makeTempDir(allocator);
defer {
removeTempDir(tmp);
allocator.free(tmp);
}
const th = try TH.init(allocator);
defer th.deinit();
const src_path = try std.fs.path.join(allocator, &.{ tmp, "before" });
defer allocator.free(src_path);
const dst_path = try std.fs.path.join(allocator, &.{ tmp, "after" });
defer allocator.free(dst_path);
try std.fs.makeDirAbsolute(src_path);
var watcher = try Watcher.init(allocator, &th.handler);
defer watcher.deinit();
try watcher.watch(tmp);
try std.fs.renameAbsolute(src_path, dst_path);
try drainEvents(Watcher, &watcher);
// All backends with emits_rename_for_dirs=true deliver at least a rename
// event for the source path. INotify delivers a paired rename callback;
// KQueue/KQueueDir deliver change(.renamed, .dir) for the old path only;
// FSEvents/Windows deliver change(.renamed, .dir) for both paths.
const has_rename = th.hasRename(src_path, dst_path) or
th.hasChange(src_path, .renamed, .dir);
try std.testing.expect(has_rename);
}
fn testUnwatchedDir(comptime Watcher: type, allocator: std.mem.Allocator) !void {
const TH = MakeTestHandler(Watcher);
@ -644,6 +685,12 @@ test "renaming a file is reported correctly per-platform" {
}
}
test "renaming a directory emits a rename event" {
inline for (comptime std.enums.values(nw.Variant)) |variant| {
try testRenameDir(nw.Create(variant), std.testing.allocator);
}
}
test "an unwatched directory produces no events" {
inline for (comptime std.enums.values(nw.Variant)) |variant| {
try testUnwatchedDir(nw.Create(variant), std.testing.allocator);