╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭────────────────────────────────────────╮ │ 3 const rp = xit.repo; │ │ 4 const hash = xit.hash; │ │ 5 const mrg = xit.merge; │ │ 6 +const obj = xit.object; │ │ 7 +const rf = xit.ref; │ │ 8 │ │ 9 pub const event_id_size: usize = 32;│ │10 │ ╰────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │53 id: [event_id_size * 2]u8, │ │54 data: EventData, │ │55 }; │ │54 - │ │55 -/// associates an Event with a git object id. │ │56 -/// we save this oid in the database so we can reliably re-consume│ │57 -/// events if their oid changed. this can happen if the branch │ │58 -/// is rebased and force-pushed. │ │59 -pub fn RepoEvent(comptime hash_kind: hash.HashKind) type { │ │60 - return struct { │ │61 - parent_oid: ?[hash.byteLen(hash_kind)]u8, │ │62 - oid: [hash.byteLen(hash_kind)]u8, │ │63 - event: ?Event, // if null, this is a merge commit │ │64 - }; │ │65 -} │ │56 │ │57 pub fn randomId(random: std.Random) [event_id_size]u8 { │ │58 var id_bytes: [event_id_size]u8 = undefined; │ ╰──────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────╮ │65 io: std.Io, │ │66 allocator: std.mem.Allocator, │ │67 repo: *rp.Repo(.xit, repo_opts), │ │78 - repo_events: []RepoEvent(repo_opts.hash), │ │68 + ref: rf.Ref, │ │69 ) !void { │ │70 const DB = rp.Repo(.xit, repo_opts).DB; │ │71 const State = rp.Repo(.xit, repo_opts).State;│ ╰─────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │74 core: *rp.Repo(.xit, repo_opts).Core, │ │75 io: std.Io, │ │76 allocator: std.mem.Allocator, │ │87 - repo_events: []RepoEvent(repo_opts.hash), │ │77 + ref: rf.Ref, │ │78 │ │79 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void {│ │80 var moment = try DB.HashMap(.read_write).init(cursor.*); │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │81 const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } };│ │92 - try consumeInTransaction(repo_opts, state, ctx.io, ctx.allocator, ctx.repo_events); │ │82 + try consumeInTransaction(repo_opts, state, ctx.io, ctx.allocator, ctx.ref); │ │83 } │ │84 }; │ │85 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────╮ │ 91 .core = &repo.core, │ │ 92 .io = io, │ │ 93 .allocator = allocator, │ │104 - .repo_events = repo_events,│ │ 94 + .ref = ref, │ │ 95 }); │ │ 96 } │ │ 97 │ ╰────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────╮ │100 state: rp.Repo(.xit, repo_opts).State(.read_write),│ │101 io: std.Io, │ │102 allocator: std.mem.Allocator, │ │113 - repo_events: []RepoEvent(repo_opts.hash), │ │103 + ref: rf.Ref, │ │104 ) !void { │ │105 const DB = rp.Repo(.xit, repo_opts).DB; │ │106 │ ╰────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │129 try haxy_moments_cursor.write(.{ .slot = last_haxy_moments_cursor.slot() }); │ │130 } │ │131 var haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │132 + │ │133 + // compute a list of events that haven't been consumed yet │ │134 + const RepoEvent = struct { │ │135 + parent_oid: ?[hash.byteLen(repo_opts.hash)]u8, │ │136 + oid: [hash.byteLen(repo_opts.hash)]u8, │ │137 + is_merge: bool, │ │138 + }; │ │139 + var repo_events: std.ArrayList(RepoEvent) = .empty; │ │140 + defer repo_events.deinit(allocator); │ │141 + { │ │142 + var commit_iter = try obj.ObjectIterator(.xit, repo_opts, .full).init(state.readOnly(), io, allocator, .{ .kind = .commit });│ │143 + defer commit_iter.deinit(); │ │144 + │ │145 + const head_oid = (try rf.readRecur(.xit, repo_opts, state.readOnly(), io, .{ .ref = ref })) orelse return error.OidNotFound; │ │146 + try commit_iter.include(&head_oid); │ │147 + │ │148 + while (try commit_iter.next()) |commit_object| { │ │149 + defer commit_object.deinit(); │ │150 + │ │151 + var oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │152 + _ = try std.fmt.hexToBytes(&oid, &commit_object.oid); │ │153 + │ │154 + // if this object id has already been consumed, skip it │ │155 + if (null != try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &oid))) { │ │156 + continue; │ │157 + } │ │158 + │ │159 + const parent_oids = commit_object.content.commit.metadata.parent_oids orelse return error.ParentOidsNotFound; │ │160 + │ │161 + if (parent_oids.len > 1) { │ │162 + // this is a merge commit │ │163 + try repo_events.append(allocator, .{ .parent_oid = null, .oid = oid, .is_merge = true }); │ │164 + } else { │ │165 + if (parent_oids.len == 0) { │ │166 + try repo_events.append(allocator, .{ .parent_oid = null, .oid = oid, .is_merge = false }); │ │167 + } else { │ │168 + var parent_oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │169 + _ = try std.fmt.hexToBytes(&parent_oid, &parent_oids[0]); │ │170 + try repo_events.append(allocator, .{ .parent_oid = parent_oid, .oid = oid, .is_merge = false }); │ │171 + } │ │172 + } │ │173 + } │ │174 + } │ │175 │ │176 // if this branch was rebased and force pushed, we need to detect that and │ │177 // properly revert the haxy state to the last valid state. we detect this │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │178 // by simply asking if the last event is a descendent of the last event │ │179 // 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; │ │180 + if (last_object_id_maybe) |*last_object_id| { │ │181 + _ = mrg.getDescendent( │ │182 + .xit, │ │183 + repo_opts, │ │184 + state.readOnly(), │ │185 + io, │ │186 + allocator, │ │187 + &std.fmt.bytesToHex(last_object_id, .lower), │ │188 + &std.fmt.bytesToHex(repo_events.items[0].oid, .lower), │ │189 + ) catch |err| switch (err) { │ │190 + error.DescendentNotFound => { │ │191 + if (repo_events.items[repo_events.items.len - 1].parent_oid) |*parent_oid| { │ │192 + const old_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, parent_oid)) orelse return error.CursorNotFound; │ │193 + const old_moment = try DB.HashMap(.read_only).init(old_moment_cursor); │ │194 │ │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]; │ │195 + const old_moment_index_cursor = try old_moment.getCursor(hash.hashInt(repo_opts.hash, "moment-index")) orelse return error.CursorNotFound; │ │196 + const old_moment_index = try old_moment_index_cursor.readUint(); │ │197 │ │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 - } │ │198 + // resize the haxy list so we truncate all the moments that were │ │199 + // created after the parent_oid was consumed │ │200 + try haxy.slice(old_moment_index + 1); │ │201 │ │161 - if (!checked_for_rebase) { │ │162 - checked_for_rebase = true; │ │202 + // make a new haxy moment and set its initial value to the last haxy moment │ │203 + haxy_moments_cursor = try haxy.appendCursor(); │ │204 + const old_haxy_moments_cursor = try haxy.getCursor(old_moment_index) orelse return error.CursorNotFound; │ │205 + try haxy_moments_cursor.write(.{ .slot = old_haxy_moments_cursor.slot() }); │ │206 + haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │207 │ │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); │ │208 + last_object_id_maybe = parent_oid.*; │ │209 + } else { │ │210 + // the branch was rebased all the way to the very beginning. │ │211 + // we have a repo event with no parent, which means it is now │ │212 + // the very first event. all we need to do is set the haxy list │ │213 + // to be empty and make a new haxy_moments map to work with. │ │214 │ │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(); │ │215 + try haxy.slice(0); │ │216 + haxy_moments_cursor = try haxy.appendCursor(); │ │217 + haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │218 │ │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 - │ │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 - │ │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 - │ │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 - │ │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; │ │219 + last_object_id_maybe = null; │ │220 } │ │221 - } │ │221 + }, │ │222 + else => |e| return e, │ │223 + }; │ │224 + } else { │ │225 + if (repo_events.items[repo_events.items.len - 1].parent_oid) |_| { │ │226 + // there is no last_object_id, but this event has a parent. │ │227 + // this is an invalid state. if the event has a parent, that │ │228 + // implies that an event has already been processed, but │ │229 + // if that's true then there would be a last_object_id. │ │230 + return error.UnexpectedParent; │ │231 } │ │232 + } │ │233 │ │234 + // iterate over the events in reverse order │ │235 + // (they are sorted by most recent events first) │ │236 + for (0..repo_events.items.len) |i| { │ │237 + const repo_event = repo_events.items[repo_events.items.len - i - 1]; │ │238 + │ │239 // create a moment for this object id │ │240 var haxy_moment_cursor = try haxy_moments.putCursor(hash.bytesToInt(repo_opts.hash, &repo_event.oid)); │ │241 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │254 // rebases starting at this object id. │ │255 try haxy_moment.put(hash.hashInt(repo_opts.hash, "moment-index"), .{ .uint = try haxy.count() - 1 }); │ │256 │ │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| { │ │257 + // consume the event unless it's a merge commit │ │258 + if (!repo_event.is_merge) { │ │259 + var commit_object = try obj.Object(.xit, repo_opts, .full).init(state.readOnly(), io, allocator, &std.fmt.bytesToHex(repo_event.oid, .lower));│ │260 + defer commit_object.deinit(); │ │261 + │ │262 + var arena = std.heap.ArenaAllocator.init(allocator); │ │263 + defer arena.deinit(); │ │264 + │ │265 + // read the message from the commit │ │266 + try commit_object.object_reader.seekTo(commit_object.content.commit.message_position); │ │267 + const message = try commit_object.object_reader.interface.allocRemaining(arena.allocator(), .unlimited); │ │268 + │ │269 + const event = try std.json.parseFromSliceLeaky(Event, arena.allocator(), message, .{}); │ │270 + │ │271 // get the id of the current event as bytes │ │272 var current_event_id: [event_id_size]u8 = undefined; │ │273 _ = try std.fmt.hexToBytes(&current_event_id, &event.id); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯