╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │78 repo_events: []RepoEvent(repo_opts.hash), │ │79 ) !void { │ │80 const DB = rp.Repo(.xit, repo_opts).DB; │ │81 + const State = rp.Repo(.xit, repo_opts).State; │ │82 │ │83 const Ctx = struct { │ │84 + core: *rp.Repo(.xit, repo_opts).Core, │ │85 io: std.Io, │ │86 allocator: std.mem.Allocator, │ │85 - repo: *rp.Repo(.xit, repo_opts), │ │87 repo_events: []RepoEvent(repo_opts.hash), │ │88 │ │89 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void {│ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 89 - const moment = try DB.HashMap(.read_write).init(cursor.*); │ │ 90 + var moment = try DB.HashMap(.read_write).init(cursor.*); │ │ 91 + const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; │ │ 92 + try consumeInTransaction(repo_opts, state, ctx.io, ctx.allocator, ctx.repo_events); │ │ 93 + } │ │ 94 + }; │ │ 95 │ │ 91 - // the last_object_id represents the object id that was last consumed │ │ 92 - var last_object_id_maybe: ?[hash.byteLen(repo_opts.hash)]u8 = null; │ │ 93 - if (try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id"))) |last_object_id_cursor| { │ │ 94 - var last_object_id_buffer: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │ 95 - _ = try last_object_id_cursor.readBytes(&last_object_id_buffer); │ │ 96 - last_object_id_maybe = last_object_id_buffer; │ │ 97 - } │ │ 96 + try repo.core.db_file.lock(io, .exclusive); │ │ 97 + defer repo.core.db_file.unlock(io); │ │ 98 │ │ 99 - // the list with all of haxy's state, including materialized views. │ │100 - // the reason it is a list is so we can keep every previous haxy │ │101 - // state, making it easy to revert to an older state if the user │ │102 - // rebases some of the past commits. │ │103 - const haxy_cursor = try moment.putCursor(hash.hashInt(repo_opts.hash, "haxy")); │ │104 - const haxy = try DB.ArrayList(.read_write).init(haxy_cursor); │ │ 99 + const history = try DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │100 + try history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │101 + .core = &repo.core, │ │102 + .io = io, │ │103 + .allocator = allocator, │ │104 + .repo_events = repo_events, │ │105 + }); │ │106 +} │ │107 │ │106 - // add a new item to the haxy list created above. │ │107 - // we call the item haxy_moments. it is a map of object id to haxy moment. │ │108 - // in other words, it maps each object id to a hash map containing the │ │109 - // state that the database was in when that object id was consumed. │ │110 - var haxy_moments_cursor = try haxy.appendCursor(); │ │111 - // use the previous haxy_moments as the basis for this one if it exists │ │112 - if (try haxy.getCursor(-2)) |last_haxy_moments_cursor| { │ │113 - try haxy_moments_cursor.write(.{ .slot = last_haxy_moments_cursor.slot() }); │ │114 - } │ │115 - var haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │108 +pub fn consumeInTransaction( │ │109 + comptime repo_opts: rp.RepoOpts(.xit), │ │110 + state: rp.Repo(.xit, repo_opts).State(.read_write), │ │111 + io: std.Io, │ │112 + allocator: std.mem.Allocator, │ │113 + repo_events: []RepoEvent(repo_opts.hash), │ │114 +) !void { │ │115 + const DB = rp.Repo(.xit, repo_opts).DB; │ │116 │ │117 - // if this branch was rebased and force pushed, we need to detect that and │ │118 - // properly revert the haxy state to the last valid state. we detect this │ │119 - // by simply asking if the last event is a descendent of the last event │ │120 - // we consumed. if it isn't, then we know the haxy state needs to be reverted. │ │121 - // from that point on, the rest of the events should be descendents so we no │ │122 - // longer need to even check. this boolean lets us know if that has happened. │ │123 - var checked_for_rebase = false; │ │117 + // the last_object_id represents the object id that was last consumed │ │118 + var last_object_id_maybe: ?[hash.byteLen(repo_opts.hash)]u8 = null; │ │119 + if (try state.extra.moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id"))) |last_object_id_cursor| { │ │120 + var last_object_id_buffer: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │121 + _ = try last_object_id_cursor.readBytes(&last_object_id_buffer); │ │122 + last_object_id_maybe = last_object_id_buffer; │ │123 + } │ │124 │ │125 - // iterate over the events in reverse order │ │126 - // (they are sorted by most recent events first) │ │127 - for (0..ctx.repo_events.len) |i| { │ │128 - const repo_event = ctx.repo_events[ctx.repo_events.len - i - 1]; │ │125 + // the list with all of haxy's state, including materialized views. │ │126 + // the reason it is a list is so we can keep every previous haxy │ │127 + // state, making it easy to revert to an older state if the user │ │128 + // rebases some of the past commits. │ │129 + const haxy_cursor = try state.extra.moment.putCursor(hash.hashInt(repo_opts.hash, "haxy")); │ │130 + const haxy = try DB.ArrayList(.read_write).init(haxy_cursor); │ │131 │ │130 - // if this object id has already been consumed, skip it │ │131 - if (null != try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &repo_event.oid))) { │ │132 - continue; │ │133 - } │ │132 + // add a new item to the haxy list created above. │ │133 + // we call the item haxy_moments. it is a map of object id to haxy moment. │ │134 + // in other words, it maps each object id to a hash map containing the │ │135 + // state that the database was in when that object id was consumed. │ │136 + var haxy_moments_cursor = try haxy.appendCursor(); │ │137 + // use the previous haxy_moments as the basis for this one if it exists │ │138 + if (try haxy.getCursor(-2)) |last_haxy_moments_cursor| { │ │139 + try haxy_moments_cursor.write(.{ .slot = last_haxy_moments_cursor.slot() }); │ │140 + } │ │141 + var haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │142 │ │135 - if (!checked_for_rebase) { │ │136 - checked_for_rebase = true; │ │143 + // if this branch was rebased and force pushed, we need to detect that and │ │144 + // properly revert the haxy state to the last valid state. we detect this │ │145 + // by simply asking if the last event is a descendent of the last event │ │146 + // we consumed. if it isn't, then we know the haxy state needs to be reverted. │ │147 + // from that point on, the rest of the events should be descendents so we no │ │148 + // longer need to even check. this boolean lets us know if that has happened. │ │149 + var checked_for_rebase = false; │ │150 │ │138 - if (last_object_id_maybe) |*last_object_id| { │ │139 - var moment_read_only = moment.readOnly(); │ │140 - const state = rp.Repo(.xit, repo_opts).State(.read_only){ .core = &ctx.repo.core, .extra = .{ .moment = &moment_read_only } }; │ │151 + // iterate over the events in reverse order │ │152 + // (they are sorted by most recent events first) │ │153 + for (0..repo_events.len) |i| { │ │154 + const repo_event = repo_events[repo_events.len - i - 1]; │ │155 │ │142 - // if the last event isn't a descendent of the last event that │ │143 - // we consumed, the branch was rebased so we need to revert the │ │144 - // haxy state back to where it was when this event's parent was │ │145 - // consumed. from that point on, we can consume the rest of the │ │146 - // events on top of that state. │ │147 - _ = mrg.getDescendent( │ │148 - .xit, │ │149 - repo_opts, │ │150 - state, │ │151 - ctx.io, │ │152 - ctx.allocator, │ │153 - &std.fmt.bytesToHex(last_object_id, .lower), │ │154 - &std.fmt.bytesToHex(ctx.repo_events[0].oid, .lower), │ │155 - ) catch |err| switch (err) { │ │156 - error.DescendentNotFound => { │ │157 - if (repo_event.parent_oid) |*parent_oid| { │ │158 - const old_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, parent_oid)) orelse return error.CursorNotFound; │ │159 - const old_moment = try DB.HashMap(.read_only).init(old_moment_cursor); │ │156 + // if this object id has already been consumed, skip it │ │157 + if (null != try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &repo_event.oid))) { │ │158 + continue; │ │159 + } │ │160 │ │161 - const old_moment_index_cursor = try old_moment.getCursor(hash.hashInt(repo_opts.hash, "moment-index")) orelse return error.CursorNotFound;│ │162 - const old_moment_index = try old_moment_index_cursor.readUint(); │ │161 + if (!checked_for_rebase) { │ │162 + checked_for_rebase = true; │ │163 │ │164 - // resize the haxy list so we truncate all the moments that were │ │165 - // created after the parent_oid was consumed │ │166 - try haxy.slice(old_moment_index + 1); │ │164 + if (last_object_id_maybe) |*last_object_id| { │ │165 + // if the last event isn't a descendent of the last event that │ │166 + // we consumed, the branch was rebased so we need to revert the │ │167 + // haxy state back to where it was when this event's parent was │ │168 + // consumed. from that point on, we can consume the rest of the │ │169 + // events on top of that state. │ │170 + _ = mrg.getDescendent( │ │171 + .xit, │ │172 + repo_opts, │ │173 + state.readOnly(), │ │174 + io, │ │175 + allocator, │ │176 + &std.fmt.bytesToHex(last_object_id, .lower), │ │177 + &std.fmt.bytesToHex(repo_events[0].oid, .lower), │ │178 + ) catch |err| switch (err) { │ │179 + error.DescendentNotFound => { │ │180 + if (repo_event.parent_oid) |*parent_oid| { │ │181 + const old_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, parent_oid)) orelse return error.CursorNotFound; │ │182 + const old_moment = try DB.HashMap(.read_only).init(old_moment_cursor); │ │183 │ │168 - // make a new haxy moment and set its initial value to the last haxy moment │ │169 - haxy_moments_cursor = try haxy.appendCursor(); │ │170 - const old_haxy_moments_cursor = try haxy.getCursor(old_moment_index) orelse return error.CursorNotFound; │ │171 - try haxy_moments_cursor.write(.{ .slot = old_haxy_moments_cursor.slot() }); │ │172 - haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │184 + const old_moment_index_cursor = try old_moment.getCursor(hash.hashInt(repo_opts.hash, "moment-index")) orelse return error.CursorNotFound; │ │185 + const old_moment_index = try old_moment_index_cursor.readUint(); │ │186 │ │174 - last_object_id_maybe = parent_oid.*; │ │175 - } else { │ │176 - // the branch was rebased all the way to the very beginning. │ │177 - // we have a repo event with no parent, which means it is now │ │178 - // the very first event. all we need to do is set the haxy list │ │179 - // to be empty and make a new haxy_moments map to work with. │ │187 + // resize the haxy list so we truncate all the moments that were │ │188 + // created after the parent_oid was consumed │ │189 + try haxy.slice(old_moment_index + 1); │ │190 │ │181 - try haxy.slice(0); │ │182 - haxy_moments_cursor = try haxy.appendCursor(); │ │183 - haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │191 + // make a new haxy moment and set its initial value to the last haxy moment │ │192 + haxy_moments_cursor = try haxy.appendCursor(); │ │193 + const old_haxy_moments_cursor = try haxy.getCursor(old_moment_index) orelse return error.CursorNotFound; │ │194 + try haxy_moments_cursor.write(.{ .slot = old_haxy_moments_cursor.slot() }); │ │195 + haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │196 │ │185 - last_object_id_maybe = null; │ │186 - } │ │187 - }, │ │188 - else => |e| return e, │ │189 - }; │ │190 - } else { │ │191 - if (repo_event.parent_oid) |_| { │ │192 - // there is no last_object_id, but this event has a parent. │ │193 - // this is an invalid state. if the event has a parent, that │ │194 - // implies that an event has already been processed, but │ │195 - // if that's true then there would be a last_object_id. │ │196 - return error.UnexpectedParent; │ │197 - } │ │198 - } │ │199 - } │ │197 + last_object_id_maybe = parent_oid.*; │ │198 + } else { │ │199 + // the branch was rebased all the way to the very beginning. │ │200 + // we have a repo event with no parent, which means it is now │ │201 + // the very first event. all we need to do is set the haxy list │ │202 + // to be empty and make a new haxy_moments map to work with. │ │203 │ │201 - // create a moment for this object id │ │202 - var haxy_moment_cursor = try haxy_moments.putCursor(hash.bytesToInt(repo_opts.hash, &repo_event.oid)); │ │204 + try haxy.slice(0); │ │205 + haxy_moments_cursor = try haxy.appendCursor(); │ │206 + haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │207 │ │204 - // if there was a previous object id, make this haxy moment's initial value to it. │ │205 - // this efficiently "clones" the map so we make further modifications based on it. │ │206 - if (last_object_id_maybe) |*last_object_id| { │ │207 - if (try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, last_object_id))) |last_haxy_moment_cursor| { │ │208 - try haxy_moment_cursor.write(.{ .slot = last_haxy_moment_cursor.slot() }); │ │209 - } │ │208 + last_object_id_maybe = null; │ │209 + } │ │210 + }, │ │211 + else => |e| return e, │ │212 + }; │ │213 + } else { │ │214 + if (repo_event.parent_oid) |_| { │ │215 + // there is no last_object_id, but this event has a parent. │ │216 + // this is an invalid state. if the event has a parent, that │ │217 + // implies that an event has already been processed, but │ │218 + // if that's true then there would be a last_object_id. │ │219 + return error.UnexpectedParent; │ │220 } │ │221 + } │ │222 + } │ │223 │ │212 - const haxy_moment = try DB.HashMap(.read_write).init(haxy_moment_cursor); │ │213 - │ │214 - // associate this moment with the index it will first appear at in the haxy list. │ │215 - // this will be important later so we can truncate that list if the user ever │ │216 - // rebases starting at this object id. │ │217 - try haxy_moment.put(hash.hashInt(repo_opts.hash, "moment-index"), .{ .uint = try haxy.count() - 1 }); │ │224 + // create a moment for this object id │ │225 + var haxy_moment_cursor = try haxy_moments.putCursor(hash.bytesToInt(repo_opts.hash, &repo_event.oid)); │ │226 │ │219 - // consume the event into the views map. │ │220 - // if the event is null, it was just a merge commit │ │221 - // so we don't need to update any views. │ │222 - if (repo_event.event) |event| { │ │223 - // get the id of the current event as bytes │ │224 - var current_event_id: [event_id_size]u8 = undefined; │ │225 - _ = try std.fmt.hexToBytes(&current_event_id, &event.id); │ │227 + // if there was a previous object id, make this haxy moment's initial value to it. │ │228 + // this efficiently "clones" the map so we make further modifications based on it. │ │229 + if (last_object_id_maybe) |*last_object_id| { │ │230 + if (try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, last_object_id))) |last_haxy_moment_cursor| { │ │231 + try haxy_moment_cursor.write(.{ .slot = last_haxy_moment_cursor.slot() }); │ │232 + } │ │233 + } │ │234 │ │227 - switch (event.data) { │ │228 - .issue => |data| { │ │229 - const event_id_to_issue_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "event-id->issue")); │ │230 - const event_id_to_issue = try DB.HashMap(.read_write).init(event_id_to_issue_cursor); │ │235 + const haxy_moment = try DB.HashMap(.read_write).init(haxy_moment_cursor); │ │236 │ │232 - const issue_cursor = try event_id_to_issue.putCursor(hash.hashInt(repo_opts.hash, &current_event_id)); │ │233 - const issue = try DB.HashMap(.read_write).init(issue_cursor); │ │237 + // associate this moment with the index it will first appear at in the haxy list. │ │238 + // this will be important later so we can truncate that list if the user ever │ │239 + // rebases starting at this object id. │ │240 + try haxy_moment.put(hash.hashInt(repo_opts.hash, "moment-index"), .{ .uint = try haxy.count() - 1 }); │ │241 │ │235 - try upsert(DB, repo_opts.hash, issue, @TypeOf(data), data); │ │236 - }, │ │237 - } │ │238 - } │ │242 + // consume the event into the views map. │ │243 + // if the event is null, it was just a merge commit │ │244 + // so we don't need to update any views. │ │245 + if (repo_event.event) |event| { │ │246 + // get the id of the current event as bytes │ │247 + var current_event_id: [event_id_size]u8 = undefined; │ │248 + _ = try std.fmt.hexToBytes(&current_event_id, &event.id); │ │249 │ │240 - // the current object id is now the last one │ │241 - last_object_id_maybe = repo_event.oid; │ │250 + switch (event.data) { │ │251 + .issue => |data| { │ │252 + const event_id_to_issue_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "event-id->issue")); │ │253 + const event_id_to_issue = try DB.HashMap(.read_write).init(event_id_to_issue_cursor); │ │254 │ │243 - // prevent any of the data created above from being mutated by future iterations of this loop │ │244 - try cursor.db.freeze(); │ │245 - } │ │255 + const issue_cursor = try event_id_to_issue.putCursor(hash.hashInt(repo_opts.hash, &current_event_id)); │ │256 + const issue = try DB.HashMap(.read_write).init(issue_cursor); │ │257 │ │247 - if (last_object_id_maybe) |*last_object_id| { │ │248 - try moment.put(hash.hashInt(repo_opts.hash, "haxy-last-object-id"), .{ .bytes = last_object_id }); │ │258 + try upsert(DB, repo_opts.hash, issue, @TypeOf(data), data); │ │259 + }, │ │260 } │ │261 } │ │251 - }; │ │262 + │ │263 + // the current object id is now the last one │ │264 + last_object_id_maybe = repo_event.oid; │ │265 │ │253 - try repo.core.db_file.lock(io, .exclusive); │ │254 - defer repo.core.db_file.unlock(io); │ │266 + // prevent any of the data created above from being mutated by future iterations of this loop │ │267 + try state.core.db.freeze(); │ │268 + } │ │269 │ │256 - // create a new transaction in the database that runs the above-defined Ctx function │ │257 - const history = try DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); │ │258 - try history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ │ │259 - .io = io, │ │260 - .allocator = allocator, │ │261 - .repo = repo, │ │262 - .repo_events = repo_events, │ │263 - }); │ │270 + if (last_object_id_maybe) |*last_object_id| { │ │271 + try state.extra.moment.put(hash.hashInt(repo_opts.hash, "haxy-last-object-id"), .{ .bytes = last_object_id }); │ │272 + } │ │273 } │ │274 │ │275 fn upsert( │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯