╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │139 }; │ │140 } │ │141 │ │142 -// bump the repo at `repo_path` to now and queue it for notifying, logging rather than returning a failure │ │142 +// bump the repo at `repo_path` to now, logging rather than returning a failure │ │143 pub fn bumpRepoActivityAt(io: std.Io, allocator: std.mem.Allocator, server: Host.Server, repo_path: []const u8) void {│ │144 const location = parseRepoPath(server.users_dir, repo_path) orelse return; │ │145 const now: u64 = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │146 bumpRepoActivity(io, allocator, server.users_dir, &location.owner_id, &location.repo_id, now) catch |err| {│ │147 std.log.warn("failed to record repo activity: {s}", .{@errorName(err)}); │ │148 }; │ │149 - if (server.dirty_repos) |dirty_repos| dirty_repos.mark(io, location); │ │149 } │ │150 │ │151 fn readActivityTime(moment: UserDB.HashMap(.read_only), repo_id: *const [event_id_size]u8) !?u64 { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │424 │ │425 pub const Server = struct { │ │426 users_dir: []const u8, │ │428 - // the notify worker's queue, null where no worker runs │ │429 - dirty_repos: ?*notify.DirtyRepos = null, │ │430 - // where repo writes resolve their participants to subscribe, null where they subscribe nobody│ │427 + // where repo writes resolve their participants and recipients, null where they notify nobody │ │428 admin_repo_path: ?[]const u8 = null, │ │429 }; │ │430 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │661 │ │662 var resolved_remote_name: ?[]u8 = null; │ │663 defer if (resolved_remote_name) |name| allocator.free(name); │ │664 + │ │665 + // the moment index a xit repo's transaction consumed into │ │666 + var moment_index: ?u64 = null; │ │667 │ │668 // a branch that only exists on a remote is consumed from its remote-tracking│ │669 // ref, while commits go on the local branch, continuing from the remote tip │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │716 events: []const EventWithId, │ │717 checks: *pch.MergeCheckInputs(repo_opts), │ │718 first_parent_oids: ?[1][hash.hexLen(repo_opts.hash)]u8, │ │719 + moment_index: *?u64, │ │720 │ │721 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void {│ │722 var moment = try DB.HashMap(.read_write).init(cursor.*); │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │723 const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │724 │ │725 try commitEvents(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.ref, ctx.events, ctx.first_parent_oids); │ │725 - if (!try consumeInTransaction(role, .xit, repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, ctx.ref)) return error.CancelTransaction;│ │726 + ctx.moment_index.* = try consumeInTransaction(role, .xit, repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, ctx.ref); │ │727 _ = try ctx.checks.update(state, ctx.io, ctx.allocator); │ │728 try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action_kind = undo_action } }); │ │729 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────╮ │741 .events = events, │ │742 .checks = &checks, │ │743 .first_parent_oids = first_parent_oids,│ │744 + .moment_index = &moment_index, │ │745 }) catch |err| switch (err) { │ │746 error.CancelTransaction => return, │ │747 else => |e| return e, │ ╰────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │756 } │ │757 │ │758 if (role == .repo and host == .server and events.len > 0) { │ │757 - // before the bump queues the repo, so its worker round sees the new subscribers │ │759 + // before delivering, so a new assignee hears of their assignment │ │760 notify.subscribeParticipants(io, allocator, host.server, repo.core.work_path, events); │ │761 + if (repo_kind == .xit) notify.deliverTransaction(io, allocator, host.server, repo_opts, repo, moment_index orelse unreachable, null, null);│ │762 bumpRepoActivityAt(io, allocator, host.server, repo.core.work_path); │ │763 } │ │764 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1029 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │1030 var moment = try DB.HashMap(.read_write).init(cursor.*); │ │1031 const state = rp.Repo(repo_kind, repo_opts).State(.read_write){ .core = &ctx.repo.core, .extra = .{} }; │ │1029 - if (!try consumeInTransaction(.repo, repo_kind, repo_opts, state, ctx.db, &moment, ctx.io, ctx.allocator, ctx.ref)) return error.CancelTransaction;│ │1032 + _ = try consumeInTransaction(.repo, repo_kind, repo_opts, state, ctx.db, &moment, ctx.io, ctx.allocator, ctx.ref); │ │1033 } │ │1034 }; │ │1035 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │1048 }; │ │1049 } │ │1050 │ │1048 -// consume the events on `ref` into the db `moment` writes to, returning false│ │1049 -// if there was nothing to do. events are read from `state`'s repo, which │ │1050 -// needn't be the repo backing the db — that's how a local (possibly │ │1051 -// git-backed) repo's events land in a standalone db. │ │1051 +// consume the events on `ref` into the db `moment` writes to, returning the │ │1052 +// moment index they landed in, or null when it only rewound to a tip consumed│ │1053 +// before. a tip already consumed cancels the transaction. events are read │ │1054 +// from `state`'s repo, which needn't be the repo backing the db — that's how │ │1055 +// a local (possibly git-backed) repo's events land in a standalone db. │ │1056 pub fn consumeInTransaction( │ │1057 comptime role: RepoRole, │ │1058 comptime repo_kind: rp.RepoKind, │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯