╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────╮ │59 issue, │ │60 comment, │ │61 }; │ │62 + │ │63 +// every logical event's kind and creation-ordered id set│ │64 +pub const event_index_key = "event-id->kind"; │ │65 +pub const event_id_set_key = "event-id-set"; │ │66 │ │67 // a null payload deletes the record │ │68 pub const Event = union(EventKind) { │ ╰─────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │223 .ref = resolved_ref, │ │224 .events = events, │ │225 .first_parent_oids = first_parent_oids, │ │226 + }) catch |err| switch (err) { │ │227 + error.CancelTransaction => {}, │ │228 + else => |e| return e, │ │229 + }; │ │230 + }, │ │231 + } │ │232 +} │ │233 + │ │234 +// merge the remote events ref into the local events ref without touching head, │ │235 +// the index, or the worktree │ │236 +pub fn mergeEvents( │ │237 + comptime repo_kind: rp.RepoKind, │ │238 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │239 + io: std.Io, │ │240 + allocator: std.mem.Allocator, │ │241 + repo: *rp.Repo(repo_kind, repo_opts), │ │242 + remote_ref: rf.Ref, │ │243 +) !void { │ │244 + switch (repo_kind) { │ │245 + .git => _ = try mergeEventsInTransaction(.git, repo_opts, .{ .core = &repo.core, .extra = .{} }, io, allocator, remote_ref), │ │246 + .xit => { │ │247 + const DB = rp.Repo(.xit, repo_opts).DB; │ │248 + const State = rp.Repo(.xit, repo_opts).State; │ │249 + │ │250 + const Ctx = struct { │ │251 + core: *rp.Repo(.xit, repo_opts).Core, │ │252 + io: std.Io, │ │253 + allocator: std.mem.Allocator, │ │254 + remote_ref: rf.Ref, │ │255 + │ │256 + pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │257 + var moment = try DB.HashMap(.read_write).init(cursor.*); │ │258 + const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │259 + if (!try mergeEventsInTransaction(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.remote_ref)) return error.CancelTransaction;│ │260 + try xit.undo.writeMessage(repo_opts, state, .{ .custom = "event" }); │ │261 + try ctx.core.chunk_store_file.sync(ctx.io); │ │262 + } │ │263 + }; │ │264 + │ │265 + try repo.core.db_file.lock(io, .exclusive); │ │266 + defer repo.core.db_file.unlock(io); │ │267 + │ │268 + const history = try DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │269 + history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │270 + .core = &repo.core, │ │271 + .io = io, │ │272 + .allocator = allocator, │ │273 + .remote_ref = remote_ref, │ │274 }) catch |err| switch (err) { │ │275 error.CancelTransaction => {}, │ │276 else => |e| return e, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │277 }; │ │278 }, │ │279 + } │ │280 +} │ │281 + │ │282 +fn mergeEventsInTransaction( │ │283 + comptime repo_kind: rp.RepoKind, │ │284 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │285 + state: rp.Repo(repo_kind, repo_opts).State(.read_write), │ │286 + io: std.Io, │ │287 + allocator: std.mem.Allocator, │ │288 + remote_ref: rf.Ref, │ │289 +) !bool { │ │290 + const remote_oid = (try rf.readRecur(repo_kind, repo_opts, state.readOnly(), io, .{ .ref = remote_ref })) orelse return false; │ │291 + const local_oid_maybe = try rf.readRecur(repo_kind, repo_opts, state.readOnly(), io, .{ .ref = events_ref }); │ │292 + │ │293 + var ref_path_buffer: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; │ │294 + const ref_path = try events_ref.toPath(&ref_path_buffer); │ │295 + const local_oid = local_oid_maybe orelse { │ │296 + try rf.write(repo_kind, repo_opts, state, io, ref_path, .{ .oid = &remote_oid }); │ │297 + return true; │ │298 + }; │ │299 + if (std.mem.eql(u8, &local_oid, &remote_oid)) return false; │ │300 + │ │301 + const ancestor_maybe: ?[hash.hexLen(repo_opts.hash)]u8 = mrg.commonAncestor(repo_kind, repo_opts, state.readOnly(), io, allocator, &local_oid, &remote_oid) catch |err| switch (err) {│ │302 + error.NoCommonAncestor => null, │ │303 + else => return err, │ │304 + }; │ │305 + if (ancestor_maybe) |ancestor| { │ │306 + if (std.mem.eql(u8, &ancestor, &remote_oid)) return false; │ │307 + if (std.mem.eql(u8, &ancestor, &local_oid)) { │ │308 + try rf.write(repo_kind, repo_opts, state, io, ref_path, .{ .oid = &remote_oid }); │ │309 + return true; │ │310 + } │ │311 } │ │312 + │ │313 + var local_commit = try obj.Object(repo_kind, repo_opts).initCommit(state.readOnly(), io, allocator, &local_oid); │ │314 + defer local_commit.deinit(); │ │315 + var remote_commit = try obj.Object(repo_kind, repo_opts).initCommit(state.readOnly(), io, allocator, &remote_oid); │ │316 + defer remote_commit.deinit(); │ │317 + if (!std.mem.eql(u8, &local_commit.content.commit.tree, &remote_commit.content.commit.tree)) return error.EventTreeConflict; │ │318 + │ │319 + const parent_oids = [_][hash.hexLen(repo_opts.hash)]u8{ local_oid, remote_oid }; │ │320 + _ = try obj.writeCommit(repo_kind, repo_opts, state, io, allocator, .{ │ │321 + .message = "merge events", │ │322 + .parent_oids = &parent_oids, │ │323 + }, null, events_ref); │ │324 + return true; │ │325 } │ │326 │ │327 // commit each event as a JSON commit message on `ref` through `state`, so a │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1142 return orderKey(std.math.maxInt(u64) - timestamp, event_id); │ │1143 } │ │1144 │ │1145 +// add or refresh one entry in the global logical-event index. updates and │ │1146 +// tombstones preserve created_ts, so the key never moves. │ │1147 +pub fn indexEvent( │ │1148 + comptime DB: type, │ │1149 + comptime hash_kind: hash.HashKind, │ │1150 + haxy_moment: DB.HashMap(.read_write), │ │1151 + event_id: *const [event_id_size]u8, │ │1152 + kind: EventKind, │ │1153 + created_ts: u64, │ │1154 +) !void { │ │1155 + const kind_map_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, event_index_key)); │ │1156 + const kind_map = try DB.HashMap(.read_write).init(kind_map_cursor); │ │1157 + try kind_map.put(hash.hashInt(hash_kind, event_id), .{ .bytes = @tagName(kind) }); │ │1158 + │ │1159 + const id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, event_id_set_key)); │ │1160 + const id_set = try DB.SortedSet(.read_write).init(id_set_cursor); │ │1161 + try id_set.put(&orderKeyDesc(created_ts, event_id)); │ │1162 +} │ │1163 + │ │1164 // the event id embedded in a set entry's order key │ │1165 pub fn readOrderKeyId(comptime DB: type, kv_pair_cursor: DB.Cursor(.read_only)) ![event_id_size]u8 {│ │1166 var cursor = kv_pair_cursor; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯