╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭────────────────────────────────────────────────────────────────╮ │68 pub const event_index_key = "event-id->kind"; │ │69 pub const active_event_id_set_key = "active-event-id-set"; │ │70 pub const removed_event_id_set_key = "removed-event-id-set";│ │71 +const event_order_key = "event-order"; │ │72 │ │73 // a null payload removes the record │ │74 pub const Event = union(EventKind) { │ ╰────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │797 │ │798 const haxy_moment = try DB.HashMap(.read_write).init(haxy_moment_cursor); │ │799 │ │800 + // graph generation gives every event a causal order independent of │ │801 + // commit timestamps. concurrent events tie-break by id in their index. │ │802 + var event_order: u64 = 1; │ │803 + for (parent_oids) |*parent_oid| { │ │804 + var parent_id: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │805 + _ = try std.fmt.hexToBytes(&parent_id, parent_oid); │ │806 + const parent_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &parent_id)) orelse return error.CursorNotFound; │ │807 + const parent = try DB.HashMap(.read_only).init(parent_cursor); │ │808 + const parent_order_cursor = try parent.getCursor(hash.hashInt(repo_opts.hash, event_order_key)) orelse return error.CursorNotFound;│ │809 + const parent_order = try parent_order_cursor.readUint(); │ │810 + event_order = @max(event_order, parent_order +| 1); │ │811 + } │ │812 + try haxy_moment.put(hash.hashInt(repo_opts.hash, event_order_key), .{ .uint = event_order }); │ │813 + │ │814 // merge changes from every parent after the first parent. the common │ │815 // ancestor is the baseline, so a later parent only contributes records │ │816 // it changed relative to the merge base. only the record maps are │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │860 // wrap the payload into the record `consume` stores, with its │ │861 // commit-derived fields; on update, `consume` preserves the │ │862 // existing record's │ │848 - const created_ts = commit_object.content.commit.metadata.timestamp; │ │863 switch (event_with_id.event) { │ │864 .user => |event_maybe| { │ │851 - const record_maybe: ?User.Record = if (event_maybe) |event| .{ .event = event, .created_ts = created_ts } else null; │ │865 + const record_maybe: ?User.Record = if (event_maybe) |event| .{ .event = event, .created_order = event_order } else null;│ │866 try User.consume(DB, repo_opts.hash, haxy_moment, &current_event_id, record_maybe, &arena, &repo_event_oid); │ │867 }, │ │868 .repo => |event_maybe| { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │855 - const record_maybe: ?Repo.Record = if (event_maybe) |event| .{ .event = event, .created_ts = created_ts } else null; │ │869 + const record_maybe: ?Repo.Record = if (event_maybe) |event| .{ .event = event, .created_order = event_order } else null;│ │870 try Repo.consume(DB, repo_opts.hash, haxy_moment, &current_event_id, record_maybe, &arena, &repo_event_oid); │ │871 }, │ │872 .issue => |event_maybe| { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │873 const record_maybe: ?Issue.Record = if (event_maybe) |event| .{ │ │874 .event = event, │ │875 .author_email = authorEmail(commit_object.content.commit.metadata.author orelse ""), │ │862 - .created_ts = created_ts, │ │876 + .created_order = event_order, │ │877 } else null; │ │878 try Issue.consume(DB, repo_opts.hash, haxy_moment, &current_event_id, record_maybe, &arena, &repo_event_oid);│ │879 }, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │881 const record_maybe: ?Discussion.Record = if (event_maybe) |event| .{ │ │882 .event = event, │ │883 .author_email = authorEmail(commit_object.content.commit.metadata.author orelse ""), │ │870 - .created_ts = created_ts, │ │884 + .created_order = event_order, │ │885 } else null; │ │886 try Discussion.consume(DB, repo_opts.hash, haxy_moment, &current_event_id, record_maybe, &arena, &repo_event_oid);│ │887 }, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │889 const record_maybe: ?Comment.Record = if (event_maybe) |event| .{ │ │890 .event = event, │ │891 .author_email = authorEmail(commit_object.content.commit.metadata.author orelse ""), │ │878 - .created_ts = created_ts, │ │892 + .created_order = event_order, │ │893 } else null; │ │894 try Comment.consume(DB, repo_opts.hash, haxy_moment, &current_event_id, record_maybe, &arena, &repo_event_oid);│ │895 }, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │899 break :blk .{ │ │900 .event = event, │ │901 .author_email = authorEmail(commit_object.content.commit.metadata.author orelse ""),│ │888 - .created_ts = created_ts, │ │902 + .created_order = event_order, │ │903 .name = entry.name, │ │904 .blob_oid = entry.oid, │ │905 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1172 const conflicts = try DB.SortedMap(.read_write).init(conflicts_cursor); │ │1173 const id_to_field_to_oid_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, T.id_to_field_to_oid_key));│ │1174 const id_to_field_to_oid = try DB.HashMap(.read_write).init(id_to_field_to_oid_cursor); │ │1161 - const merged_key = orderKeyDesc(merged.created_ts, &event_id); │ │1175 + const merged_key = orderKeyDesc(merged.created_order, &event_id); │ │1176 │ │1177 var parent_field_oids: ?DB.SortedMap(.read_only) = null; │ │1178 if (parent_id_to_field_to_oid) |map| { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │1232 continue; │ │1233 } │ │1234 │ │1221 - // created_ts never changes, so the parent keys this record's conflict │ │1235 + // created_order never changes, so the parent keys this record's conflict│ │1236 // identically, and it carries over when the target has none, unless the │ │1237 // parent's entry is unchanged from the baseline, meaning the target │ │1238 // inherited it too and resolving it removed it here. │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯