╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────╮ │ 2 const xit = @import("xit"); │ │ 3 const rp = xit.repo; │ │ 4 const hash = xit.hash; │ │ 5 +const mrg = xit.merge; │ │ 6 │ │ 6 -const event_id_size: usize = 32; │ │ 7 +pub const event_id_size: usize = 32;│ │ 8 │ │ 9 pub const EventKind = enum { │ │10 issue, │ ╰────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────╮ │60 return struct { │ │61 parent_oid: ?[hash.byteLen(hash_kind)]u8, │ │62 oid: [hash.byteLen(hash_kind)]u8, │ │62 - event: Event, │ │63 + event: ?Event, // if null, this is a merge commit│ │64 }; │ │65 } │ │66 │ ╰─────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │71 } │ │72 │ │73 pub fn consume( │ │73 - comptime DB: type, │ │74 - comptime hash_kind: hash.HashKind, │ │74 + comptime repo_opts: rp.RepoOpts(.xit), │ │75 io: std.Io, │ │76 - db: *DB, │ │77 - db_file: std.Io.File, │ │78 - repo_events: []RepoEvent(hash_kind), │ │76 + allocator: std.mem.Allocator, │ │77 + repo: *rp.Repo(.xit, repo_opts), │ │78 + repo_events: []RepoEvent(repo_opts.hash), │ │79 ) !void { │ │80 + const DB = rp.Repo(.xit, repo_opts).DB; │ │81 + │ │82 const Ctx = struct { │ │81 - repo_events: []RepoEvent(hash_kind), │ │83 + io: std.Io, │ │84 + allocator: std.mem.Allocator, │ │85 + repo: *rp.Repo(.xit, repo_opts), │ │86 + repo_events: []RepoEvent(repo_opts.hash), │ │87 │ │88 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void {│ │89 const moment = try DB.HashMap(.read_write).init(cursor.*); │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │90 │ │91 // the last_object_id represents the object id that was last consumed │ │87 - var last_object_id_maybe: ?[hash.byteLen(hash_kind)]u8 = null; │ │88 - if (try moment.getCursor(hash.hashInt(hash_kind, "haxy-last-object-id"))) |last_object_id_cursor| { │ │89 - var last_object_id_buffer: [hash.byteLen(hash_kind)]u8 = undefined; │ │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 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │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. │ │ 98 - const haxy_cursor = try moment.putCursor(hash.hashInt(hash_kind, "haxy")); │ │103 + const haxy_cursor = try moment.putCursor(hash.hashInt(repo_opts.hash, "haxy"));│ │104 const haxy = try DB.ArrayList(.read_write).init(haxy_cursor); │ │105 │ │106 // add a new item to the haxy list created above. │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │114 } │ │115 var haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │116 │ │112 - // for each event we want to consume... │ │113 - for (ctx.repo_events) |repo_event| { │ │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; │ │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]; │ │129 + │ │130 // if this object id has already been consumed, skip it │ │115 - if (null != try haxy_moments.getCursor(hash.bytesToInt(hash_kind, &repo_event.oid))) { │ │131 + if (null != try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &repo_event.oid))) {│ │132 continue; │ │133 } │ │134 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │119 - // compare last_object_id to the event's parent. this is important │ │120 - // in situations where the branch was rebased, because in that case │ │121 - // the last_object_id may no longer be valid. │ │122 - if (last_object_id_maybe) |*last_object_id| { │ │123 - if (repo_event.parent_oid) |*parent_oid| { │ │124 - if (!std.mem.eql(u8, last_object_id, parent_oid)) { │ │125 - // the last_object_id does not match the current event's parent id. │ │126 - // this means that a rebase occurred. we just need to revert the │ │127 - // haxy list to the state it was in what parent_oid was consumed, │ │128 - // and then create a new haxy_moments map to work with. │ │135 + if (!checked_for_rebase) { │ │136 + checked_for_rebase = true; │ │137 │ │130 - const old_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(hash_kind, parent_oid)) orelse return error.CursorNotFound; │ │131 - const old_moment = try DB.HashMap(.read_only).init(old_moment_cursor); │ │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 } }; │ │141 │ │133 - const old_moment_index_cursor = try old_moment.getCursor(hash.hashInt(hash_kind, "moment-index")) orelse return error.CursorNotFound; │ │134 - const old_moment_index = try old_moment_index_cursor.readUint(); │ │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); │ │160 │ │136 - // resize the haxy list so we truncate all the moments that were │ │137 - // created after the parent_oid was consumed │ │138 - try haxy.slice(old_moment_index + 1); │ │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(); │ │163 │ │140 - // make a new haxy moment and set its initial value to the last haxy moment │ │141 - haxy_moments_cursor = try haxy.appendCursor(); │ │142 - const old_haxy_moments_cursor = try haxy.getCursor(old_moment_index) orelse return error.CursorNotFound; │ │143 - try haxy_moments_cursor.write(.{ .slot = old_haxy_moments_cursor.slot() }); │ │144 - haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │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); │ │167 + │ │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); │ │173 │ │146 - last_object_id_maybe = parent_oid.*; │ │147 - } │ │148 - } else { │ │149 - // the branch was rebased all the way to the very beginning. │ │150 - // we have a repo event with no parent, which means it is now │ │151 - // the very first event. all we need to do is set the haxy list │ │152 - // to be empty and make a new haxy_moments map to work with. │ │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. │ │180 │ │154 - try haxy.slice(0); │ │155 - haxy_moments_cursor = try haxy.appendCursor(); │ │156 - haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │181 + try haxy.slice(0); │ │182 + haxy_moments_cursor = try haxy.appendCursor(); │ │183 + haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │184 │ │158 - last_object_id_maybe = null; │ │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 } │ │160 - } else { │ │161 - if (repo_event.parent_oid) |_| { │ │162 - // there is no last_object_id, but this event has a parent. │ │163 - // this is an invalid state. if the event has a parent, that │ │164 - // implies that an event has already been processed, but │ │165 - // if that's true then there would be a last_object_id. │ │166 - return error.UnexpectedParent; │ │167 - } │ │199 } │ │200 │ │201 // create a moment for this object id │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │171 - var haxy_moment_cursor = try haxy_moments.putCursor(hash.bytesToInt(hash_kind, &repo_event.oid)); │ │202 + var haxy_moment_cursor = try haxy_moments.putCursor(hash.bytesToInt(repo_opts.hash, &repo_event.oid));│ │203 │ │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| { │ │176 - if (try haxy_moments.getCursor(hash.bytesToInt(hash_kind, last_object_id))) |last_haxy_moment_cursor| { │ │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 } │ │210 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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. │ │186 - try haxy_moment.put(hash.hashInt(hash_kind, "moment-index"), .{ .uint = try haxy.count() - 1 }); │ │217 + try haxy_moment.put(hash.hashInt(repo_opts.hash, "moment-index"), .{ .uint = try haxy.count() - 1 });│ │218 │ │188 - // consume the event into the views map │ │189 - { │ │190 - const event = repo_event.event; │ │191 - │ │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); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯