╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │290 .event = event, │ │291 }}); │ │292 } │ │293 + │ │294 +pub const undo_action = "haxy/events"; │ │295 +pub const merge_undo_action = "haxy/merge-events"; │ │296 │ │297 +// index that tracks the events consumed in a given haxy moment │ │298 +pub const moment_index_to_event_id_set_key = "moment-index->event-id-set"; │ │299 + │ │300 +// a haxy moment's own position in the haxy history │ │301 +pub const moment_index_key = "moment-index"; │ │302 + │ │303 +// a moment's key in the set map │ │304 +fn momentIndexKey(comptime hash_kind: hash.HashKind, moment_index: u64) hash.HashInt(hash_kind) { │ │305 + var buffer: [@sizeOf(u64)]u8 = undefined; │ │306 + std.mem.writeInt(u64, &buffer, moment_index, .big); │ │307 + return hash.hashInt(hash_kind, &buffer); │ │308 +} │ │309 + │ │310 +// the ids of the events a moment wrote, newest first, or null when it wrote none │ │311 +pub fn momentEventIds( │ │312 + comptime DB: type, │ │313 + comptime hash_kind: hash.HashKind, │ │314 + haxy_moment: DB.HashMap(.read_only), │ │315 + moment_index: u64, │ │316 +) !?DB.SortedSet(.read_only) { │ │317 + const sets_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, moment_index_to_event_id_set_key)) orelse return null;│ │318 + const sets = try DB.HashMap(.read_only).init(sets_cursor); │ │319 + const ids_cursor = try sets.getCursor(momentIndexKey(hash_kind, moment_index)) orelse return null; │ │320 + return try DB.SortedSet(.read_only).init(ids_cursor); │ │321 +} │ │322 + │ │323 // commit `events` (if any) as JSON commit messages on `ref`, then consume the │ │324 // events on `ref` into the db the repo's views read: the repo's own db for a │ │325 // xit repo, or the standalone event db next to a git repo. for a xit repo the │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │297 -// commits and the consume run in one transaction. server writes refresh│ │298 -// mergeability after the transaction releases its lock. │ │326 +// commits and the consume run in one transaction, which also saves the │ │327 +// mergeability of the patches the events touched. │ │328 pub fn consume( │ │329 host_kind: HostKind, │ │330 comptime role: RepoRole, │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │378 .xit => { │ │379 const DB = rp.Repo(.xit, repo_opts).DB; │ │380 const State = rp.Repo(.xit, repo_opts).State; │ │381 + var checks: pch.MergeCheckInputs(repo_opts) = .{}; │ │382 + defer checks.deinit(io, allocator); │ │383 + if (role == .repo and host_kind == .server) { │ │384 + checks.prepare(io, allocator, repo, events) catch |err| { │ │385 + std.log.warn("failed to prepare mergeability: {s}", .{@errorName(err)});│ │386 + }; │ │387 + } │ │388 │ │389 const Ctx = struct { │ │390 core: *rp.Repo(.xit, repo_opts).Core, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │392 allocator: std.mem.Allocator, │ │393 ref: rf.Ref, │ │394 events: []const EventWithId, │ │395 + checks: *pch.MergeCheckInputs(repo_opts), │ │396 first_parent_oids: ?[1][hash.hexLen(repo_opts.hash)]u8, │ │397 │ │398 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void {│ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │401 │ │402 try commitEvents(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.ref, ctx.events, ctx.first_parent_oids); │ │403 if (!try consumeInTransaction(role, .xit, repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, ctx.ref)) return error.CancelTransaction;│ │367 - try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action = "event" } }); │ │404 + _ = try ctx.checks.update(state, ctx.io, ctx.allocator); │ │405 + try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action = undo_action } }); │ │406 } │ │407 }; │ │408 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────╮ │416 .allocator = allocator, │ │417 .ref = resolved_ref, │ │418 .events = events, │ │419 + .checks = &checks, │ │420 .first_parent_oids = first_parent_oids,│ │421 }) catch |err| switch (err) { │ │422 error.CancelTransaction => return, │ ╰────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │425 }, │ │426 } │ │427 │ │389 - if (role == .repo and repo_kind == .xit and host_kind == .server) { │ │390 - // since patches may have been updated, we need to recheck their mergeability │ │391 - if (events.len == 0) { │ │392 - // we consumed events already on the ref, and we don't know what specific │ │393 - // patches may have been updated, so refresh all of them │ │394 - pch.refreshMergeability(repo_opts, io, allocator, repo, null); │ │395 - } else { │ │396 - // we consumed specific events so we can refresh only the patches that were updated│ │397 - for (events) |event| { │ │398 - if (event.event != .patch) continue; │ │399 - const id = parseEventId(&event.id) catch continue; │ │400 - pch.refreshMergeability(repo_opts, io, allocator, repo, id); │ │401 - } │ │402 - } │ │428 + // consuming an existing ref does not provide the set of changed patches │ │429 + // explicit patch writes already saved their checks in the event transaction │ │430 + if (role == .repo and repo_kind == .xit and host_kind == .server and events.len == 0) { │ │431 + pch.refreshMergeability(repo_opts, io, allocator, repo, null); │ │432 } │ │433 } │ │434 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │458 var moment = try DB.HashMap(.read_write).init(cursor.*); │ │459 const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │460 if (!try mergeEventsInTransaction(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.remote_ref)) return error.CancelTransaction; │ │432 - try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action = "event" } }); │ │461 + try xit.undo.write(repo_opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .custom = .{ .action = merge_undo_action } });│ │462 } │ │463 }; │ │464 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │784 const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &head_oid)) orelse return error.CursorNotFound; │ │785 const haxy_moment = try DB.HashMap(.read_only).init(haxy_moment_cursor); │ │786 │ │758 - const moment_index_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "moment-index")) orelse return error.CursorNotFound; │ │787 + const moment_index_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, moment_index_key)) orelse return error.CursorNotFound;│ │788 const moment_index = try moment_index_cursor.readUint(); │ │789 │ │790 try haxy_history.slice(moment_index + 1); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │840 const old_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &oid)) orelse return error.CursorNotFound; │ │841 const old_moment = try DB.HashMap(.read_only).init(old_moment_cursor); │ │842 │ │814 - const old_moment_index_cursor = try old_moment.getCursor(hash.hashInt(repo_opts.hash, "moment-index")) orelse return error.CursorNotFound; │ │843 + const old_moment_index_cursor = try old_moment.getCursor(hash.hashInt(repo_opts.hash, moment_index_key)) orelse return error.CursorNotFound;│ │844 const old_moment_index = try old_moment_index_cursor.readUint(); │ │845 │ │846 // resize the haxy history list so we truncate all the │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯