╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │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 + var arena = std.heap.ArenaAllocator.init(allocator); │ │134 + defer arena.deinit(); │ │135 │ │136 // compute a list of events that haven't been consumed yet │ │137 const RepoEvent = struct { │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │135 - parent_oid: ?[hash.byteLen(repo_opts.hash)]u8, │ │138 + parent_oids: []const [hash.hexLen(repo_opts.hash)]u8,│ │139 oid: [hash.byteLen(repo_opts.hash)]u8, │ │137 - is_merge: bool, │ │140 }; │ │141 var repo_events: std.ArrayList(RepoEvent) = .empty; │ │142 defer repo_events.deinit(allocator); │ ╰──────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │147 const head_oid = (try rf.readRecur(.xit, repo_opts, state.readOnly(), io, .{ .ref = ref })) orelse return error.OidNotFound;│ │148 try commit_iter.include(&head_oid); │ │149 │ │148 - while (try commit_iter.next()) |commit_object| { │ │149 - defer commit_object.deinit(); │ │150 - │ │150 + while (try commit_iter.next(arena.allocator())) |commit_object| { │ │151 var oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │152 _ = try std.fmt.hexToBytes(&oid, &commit_object.oid); │ │153 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 - } │ │160 + try repo_events.append(allocator, .{ .parent_oids = parent_oids, .oid = oid }); │ │161 } │ │162 + } │ │163 + │ │164 + // if there are no events to process, look at the oid at HEAD and update │ │165 + // the last_object_id to point to it. this is important in situations │ │166 + // where we do a merge and then force-push to remove the merge. see the │ │167 + // merge test for an example. │ │168 + if (repo_events.items.len == 0) { │ │169 + const head_oid_hex = (try rf.readRecur(.xit, repo_opts, state.readOnly(), io, .{ .ref = ref })) orelse return error.OidNotFound; │ │170 + var head_oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │171 + _ = try std.fmt.hexToBytes(&head_oid, &head_oid_hex); │ │172 + │ │173 + const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &head_oid)) orelse return error.CursorNotFound; │ │174 + const haxy_moment = try DB.HashMap(.read_only).init(haxy_moment_cursor); │ │175 + │ │176 + const moment_index_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "moment-index")) orelse return error.CursorNotFound;│ │177 + const moment_index = try moment_index_cursor.readUint(); │ │178 + │ │179 + try haxy.slice(moment_index + 1); │ │180 + try state.extra.moment.put(hash.hashInt(repo_opts.hash, "haxy-last-object-id"), .{ .bytes = &head_oid }); │ │181 + return; │ │182 } │ │175 - if (repo_events.items.len == 0) return; │ │183 │ │184 // if this branch was rebased and force pushed, we need to detect that and │ │185 // properly revert the haxy state to the last valid state. we detect this │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │186 // by simply asking if the last event is a descendent of the last event │ │187 // we consumed. if it isn't, then we know the haxy state needs to be reverted.│ │188 if (last_object_id_maybe) |*last_object_id| { │ │189 + var is_rebased = false; │ │190 + │ │191 _ = mrg.getDescendent( │ │192 .xit, │ │193 repo_opts, │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │197 &std.fmt.bytesToHex(last_object_id, .lower), │ │198 &std.fmt.bytesToHex(repo_events.items[0].oid, .lower), │ │199 ) catch |err| switch (err) { │ │191 - error.DescendentNotFound => { │ │192 - if (repo_events.items[repo_events.items.len - 1].parent_oid) |*parent_oid| { │ │193 - const old_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, parent_oid)) orelse return error.CursorNotFound; │ │200 + error.DescendentNotFound => is_rebased = true, │ │201 + else => |e| return e, │ │202 + }; │ │203 + │ │204 + if (is_rebased) { │ │205 + const parent_oids = repo_events.items[repo_events.items.len - 1].parent_oids; │ │206 + switch (parent_oids.len) { │ │207 + 0 => { │ │208 + // the branch was rebased all the way to the very beginning. │ │209 + // we have a repo event with no parent, which means it is now │ │210 + // the very first event. all we need to do is set the haxy list │ │211 + // to be empty and make a new haxy_moments map to work with. │ │212 + │ │213 + try haxy.slice(0); │ │214 + haxy_moments_cursor = try haxy.appendCursor(); │ │215 + haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │216 + │ │217 + last_object_id_maybe = null; │ │218 + }, │ │219 + 1 => { │ │220 + var oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │221 + _ = try std.fmt.hexToBytes(&oid, &parent_oids[0]); │ │222 + │ │223 + const old_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &oid)) orelse return error.CursorNotFound; │ │224 const old_moment = try DB.HashMap(.read_only).init(old_moment_cursor); │ │225 │ │226 const old_moment_index_cursor = try old_moment.getCursor(hash.hashInt(repo_opts.hash, "moment-index")) orelse return error.CursorNotFound;│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │236 try haxy_moments_cursor.write(.{ .slot = old_haxy_moments_cursor.slot() });│ │237 haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │238 │ │209 - last_object_id_maybe = parent_oid.*; │ │210 - } else { │ │211 - // the branch was rebased all the way to the very beginning. │ │212 - // we have a repo event with no parent, which means it is now │ │213 - // the very first event. all we need to do is set the haxy list │ │214 - // to be empty and make a new haxy_moments map to work with. │ │215 - │ │216 - try haxy.slice(0); │ │217 - haxy_moments_cursor = try haxy.appendCursor(); │ │218 - haxy_moments = try DB.HashMap(.read_write).init(haxy_moments_cursor); │ │219 - │ │220 - last_object_id_maybe = null; │ │221 - } │ │222 - }, │ │223 - else => |e| return e, │ │224 - }; │ │239 + last_object_id_maybe = oid; │ │240 + }, │ │241 + else => return error.UnexpectedParentCount, │ │242 + } │ │243 + } │ │244 } else { │ │226 - if (repo_events.items[repo_events.items.len - 1].parent_oid) |_| { │ │245 + if (repo_events.items[repo_events.items.len - 1].parent_oids.len != 0) { │ │246 // there is no last_object_id, but this event has a parent. │ │247 // this is an invalid state. if the event has a parent, that │ │248 // implies that an event has already been processed, but │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │259 // create a moment for this object id │ │260 var haxy_moment_cursor = try haxy_moments.putCursor(hash.bytesToInt(repo_opts.hash, &repo_event.oid)); │ │261 │ │243 - // if there was a previous object id, make this haxy moment's initial value to it. │ │262 + // if there was a previous object id, set this haxy moment's initial value to it. │ │263 // this efficiently "clones" the map so we make further modifications based on it. │ │245 - if (last_object_id_maybe) |*last_object_id| { │ │246 - if (try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, last_object_id))) |last_haxy_moment_cursor| { │ │247 - try haxy_moment_cursor.write(.{ .slot = last_haxy_moment_cursor.slot() }); │ │248 - } │ │264 + if (repo_event.parent_oids.len > 0) { │ │265 + var first_parent_oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │266 + _ = try std.fmt.hexToBytes(&first_parent_oid, &repo_event.parent_oids[0]); │ │267 + │ │268 + const first_parent_haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &first_parent_oid)) orelse return error.CursorNotFound;│ │269 + try haxy_moment_cursor.write(.{ .slot = first_parent_haxy_moment_cursor.slot() }); │ │270 } │ │271 │ │272 const haxy_moment = try DB.HashMap(.read_write).init(haxy_moment_cursor); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │273 │ │274 + // merge changes from every parent after the first parent. the first parent │ │275 + // is the baseline, so a later parent only contributes values it changed. │ │276 + if (repo_event.parent_oids.len > 1) { │ │277 + var first_parent_oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │278 + _ = try std.fmt.hexToBytes(&first_parent_oid, &repo_event.parent_oids[0]); │ │279 + const first_parent_haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &first_parent_oid)) orelse return error.CursorNotFound;│ │280 + const first_parent_haxy_moment = try DB.HashMap(.read_only).init(first_parent_haxy_moment_cursor); │ │281 + │ │282 + for (repo_event.parent_oids[1..]) |*parent_oid| { │ │283 + var oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │284 + _ = try std.fmt.hexToBytes(&oid, parent_oid); │ │285 + │ │286 + const parent_haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &oid)) orelse return error.CursorNotFound; │ │287 + const parent_haxy_moment = try DB.HashMap(.read_only).init(parent_haxy_moment_cursor); │ │288 + │ │289 + try mergeChangedMapEntries(DB, haxy_moment, parent_haxy_moment, first_parent_haxy_moment, true); │ │290 + } │ │291 + } │ │292 + │ │293 // associate this moment with the index it will first appear at in the haxy list. │ │294 // this will be important later so we can truncate that list if the user ever │ │295 // rebases starting at this object id. │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │296 try haxy_moment.put(hash.hashInt(repo_opts.hash, "moment-index"), .{ .uint = try haxy.count() - 1 }); │ │297 │ │298 // consume the event unless it's a merge commit │ │259 - if (!repo_event.is_merge) { │ │299 + if (repo_event.parent_oids.len <= 1) { │ │300 var commit_object = try obj.Object(.xit, repo_opts, .full).init(state.readOnly(), io, allocator, &std.fmt.bytesToHex(repo_event.oid, .lower));│ │301 defer commit_object.deinit(); │ │262 - │ │263 - var arena = std.heap.ArenaAllocator.init(allocator); │ │264 - defer arena.deinit(); │ │302 │ │303 // read the message from the commit │ │304 try commit_object.object_reader.seekTo(commit_object.content.commit.message_position); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯