╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────╮ │1 const std = @import("std"); │ │2 +const xit = @import("xit"); │ │3 +const rp = xit.repo; │ │4 +const hash = xit.hash; │ │5 │ │3 -pub const id_size: usize = 32; │ │6 +const event_id_size: usize = 32; │ │7 │ │8 pub const EventData = union(enum) {│ │9 issue: struct { │ ╰──────────────────────────────────────╯ ╭──────────────────────────────────╮ │14 }; │ │15 │ │16 pub const Event = struct { │ │14 - id: [id_size * 2]u8, │ │17 + id: [event_id_size * 2]u8,│ │18 data: EventData, │ │19 }; │ │20 │ ╰──────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │18 -pub fn randomId(random: std.Random) [id_size]u8 { │ │19 - var id_bytes: [id_size]u8 = undefined; │ │21 +/// associates an Event with a git object id. │ │22 +/// we save this oid in the database so we can reliably re-consume│ │23 +/// events if their oid changed. this can happen if the branch │ │24 +/// is rebased and force-pushed. │ │25 +pub fn RepoEvent(comptime hash_kind: hash.HashKind) type { │ │26 + return struct { │ │27 + oid: [hash.byteLen(hash_kind)]u8, │ │28 + event: Event, │ │29 + }; │ │30 +} │ │31 + │ │32 +pub fn randomId(random: std.Random) [event_id_size]u8 { │ │33 + var id_bytes: [event_id_size]u8 = undefined; │ │34 random.bytes(&id_bytes); │ │35 return id_bytes; │ │36 } │ ╰──────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 37 + │ │ 38 +pub fn consume( │ │ 39 + comptime DB: type, │ │ 40 + comptime hash_kind: hash.HashKind, │ │ 41 + io: std.Io, │ │ 42 + db: *DB, │ │ 43 + db_file: std.Io.File, │ │ 44 + repo_events: []RepoEvent(hash_kind), │ │ 45 +) !void { │ │ 46 + const Ctx = struct { │ │ 47 + repo_events: []RepoEvent(hash_kind), │ │ 48 + │ │ 49 + pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { │ │ 50 + const moment = try DB.HashMap(.read_write).init(cursor.*); │ │ 51 + │ │ 52 + // the map with all of haxy's state and materialized views │ │ 53 + const haxy_cursor = try moment.putCursor(hash.hashInt(hash_kind, "haxy")); │ │ 54 + const haxy = try DB.HashMap(.read_write).init(haxy_cursor); │ │ 55 │ │ 56 + // try reading the last object id that was consumed │ │ 57 + var last_object_id_maybe: ?[hash.byteLen(hash_kind)]u8 = null; │ │ 58 + if (try haxy.getCursor(hash.hashInt(hash_kind, "last-object-id"))) |last_object_id_cursor| { │ │ 59 + var last_object_id_buffer: [hash.byteLen(hash_kind)]u8 = undefined; │ │ 60 + _ = try last_object_id_cursor.readBytes(&last_object_id_buffer); │ │ 61 + last_object_id_maybe = last_object_id_buffer; │ │ 62 + } │ │ 63 + │ │ 64 + // for each event we want to consume... │ │ 65 + for (ctx.repo_events) |repo_event| { │ │ 66 + // map with the views as they appeared when each event was consumed. │ │ 67 + // we can use this to see (and revert) the views to any previous state. │ │ 68 + const object_id_to_views_cursor = try haxy.putCursor(hash.hashInt(hash_kind, "object-id->views")); │ │ 69 + const object_id_to_views = try DB.HashMap(.read_write).init(object_id_to_views_cursor); │ │ 70 + │ │ 71 + // create a new views map for the current event we are consuming │ │ 72 + var views_cursor = try object_id_to_views.putCursor(hash.bytesToInt(hash_kind, &repo_event.oid)); │ │ 73 + │ │ 74 + // if there was a previous event, set the views map to have the same value as it. │ │ 75 + // this efficiently "clones" the map so we make further modifications based on it. │ │ 76 + if (last_object_id_maybe) |*last_object_id| { │ │ 77 + if (try object_id_to_views.getCursor(hash.bytesToInt(hash_kind, last_object_id))) |last_view_cursor| { │ │ 78 + try views_cursor.write(.{ .slot = last_view_cursor.slot() }); │ │ 79 + } │ │ 80 + } │ │ 81 + │ │ 82 + // consume the event into the views map │ │ 83 + { │ │ 84 + const event = repo_event.event; │ │ 85 + │ │ 86 + // get the id of the current event as bytes │ │ 87 + var current_event_id: [event_id_size]u8 = undefined; │ │ 88 + _ = try std.fmt.hexToBytes(&current_event_id, &event.id); │ │ 89 + │ │ 90 + const views = try DB.HashMap(.read_write).init(views_cursor); │ │ 91 + │ │ 92 + switch (event.data) { │ │ 93 + .issue => |data| { │ │ 94 + const event_id_to_issue_cursor = try views.putCursor(hash.hashInt(hash_kind, "event-id->issue"));│ │ 95 + const event_id_to_issue = try DB.HashMap(.read_write).init(event_id_to_issue_cursor); │ │ 96 + │ │ 97 + const issue_cursor = try event_id_to_issue.putCursor(hash.hashInt(hash_kind, &current_event_id));│ │ 98 + const issue = try DB.HashMap(.read_write).init(issue_cursor); │ │ 99 + │ │100 + try putIfDifferent(issue, hash.hashInt(hash_kind, "title"), data.title); │ │101 + try putIfDifferent(issue, hash.hashInt(hash_kind, "description"), data.description); │ │102 + }, │ │103 + } │ │104 + } │ │105 + │ │106 + // the current object id is now the last one │ │107 + last_object_id_maybe = repo_event.oid; │ │108 + │ │109 + // prevent any of the data created above from being mutated by future iterations of this loop │ │110 + try cursor.db.freeze(); │ │111 + } │ │112 + │ │113 + if (last_object_id_maybe) |*last_object_id| { │ │114 + try haxy.put(hash.hashInt(hash_kind, "last-object-id"), .{ .bytes = last_object_id }); │ │115 + } │ │116 + } │ │117 + │ │118 + fn putIfDifferent(map: DB.HashMap(.read_write), key: hash.HashInt(hash_kind), value: []const u8) !void { │ │119 + if (try map.getCursor(key)) |value_cursor| { │ │120 + var buffer: [4096]u8 = undefined; │ │121 + const existing_value = try value_cursor.readBytes(&buffer); │ │122 + if (std.mem.eql(u8, existing_value, value)) { │ │123 + return; │ │124 + } │ │125 + } │ │126 + │ │127 + try map.put(key, .{ .bytes = value }); │ │128 + } │ │129 + }; │ │130 + │ │131 + try db_file.lock(io, .exclusive); │ │132 + defer db_file.unlock(io); │ │133 + │ │134 + // create a new transaction in the database that runs the above-defined Ctx function │ │135 + const history = try DB.ArrayList(.read_write).init(db.rootCursor()); │ │136 + try history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ .repo_events = repo_events }); │ │137 +} │ │138 + │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯