╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │48 id: [event_id_size * 2]u8, │ │49 event: Event, │ │50 timestamp: u64 = 0, // not serialized, because it comes from the commit timestamp│ │51 + author_email: []const u8, // not serialized, because it goes in the commit author│ │52 │ │53 pub fn jsonStringify(self: EventWithId, jw: anytype) !void { │ │54 try jw.beginObject(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │72 const json_event = try std.json.parseFromSliceLeaky(JsonEvent, arena.allocator(), message, .{ .ignore_unknown_fields = true });│ │73 return .{ │ │74 .id = json_event.id, │ │75 + // unused when reading; the author lives in the commit │ │76 + .author_email = "", │ │77 .event = switch (json_event.kind) { │ │78 .user => .{ │ │79 .user = if (json_event.data) |value| │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │213 json.clearRetainingCapacity(); │ │214 try std.json.Stringify.value(event, .{ .whitespace = .indent_2 }, &json.writer); │ │215 if (json.written().len > max_event_size) return error.EventTooLarge; │ │213 - _ = try obj.writeCommit(repo_kind, repo_opts, state, io, allocator, .{ .author = "haxy <user@haxy>", .message = json.written(), .timestamp = event.timestamp, .parent_oids = parent_oids }, null, ref);│ │216 + const author = try std.fmt.allocPrint(allocator, "haxy <{s}>", .{event.author_email}); │ │217 + defer allocator.free(author); │ │218 + _ = try obj.writeCommit(repo_kind, repo_opts, state, io, allocator, .{ .author = author, .message = json.written(), .timestamp = event.timestamp, .parent_oids = parent_oids }, null, ref); │ │219 // later events parent on the ref's new tip │ │220 parent_oids = null; │ │221 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1037 var owner_user_id: [event_id_size]u8 = undefined; │ │1038 _ = try owner_id_cursor.readBytes(&owner_user_id); │ │1039 │ │1040 + const owner = (try User.readById(AdminDB, admin_repo_opts.hash, moment, &arena, &owner_user_id)) orelse unreachable;│ │1041 + │ │1042 var id_bytes: [event_id_size]u8 = undefined; │ │1043 io.random(&id_bytes); │ │1044 const event_id_hex = std.fmt.bytesToHex(id_bytes, .lower); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │1046 try consume(.xit, admin_repo_opts, io, allocator, &repo, events_ref, &[_]EventWithId{.{│ │1047 .id = event_id_hex, │ │1048 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │1049 + .author_email = owner.email, │ │1050 .event = .{ .repo = .{ │ │1051 .user_id = &owner_user_id, │ │1052 .name = repo_name, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/event/Issue.zig│ ╰───────────────────╯ ╭─────────────────────────────────────────────────────────────╮ │200 repo: *rp.Repo(repo_kind, repo_opts), │ │201 id_bytes: *const [evt.event_id_size]u8, │ │202 change: Update, │ │203 + author_email: []const u8, │ │204 ) !void { │ │205 var arena = std.heap.ArenaAllocator.init(allocator);│ │206 defer arena.deinit(); │ ╰─────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │223 try evt.consume(repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &[_]evt.EventWithId{.{│ │224 .id = std.fmt.bytesToHex(id_bytes.*, .lower), │ │225 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │226 + .author_email = author_email, │ │227 .event = .{ .issue = updated }, │ │228 }}); │ │229 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │24 pub const conflicts_key = "conflicted-user-id->conflict"; │ │25 pub const id_to_field_to_oid_key = "user-id->field->oid"; │ │26 │ │27 +// resolves a commit's author email to its user at read time │ │28 +pub const email_to_user_id_key = "email->user-id"; │ │29 + │ │30 // a user's key in the name index │ │31 pub fn indexKey(self: Self, allocator: std.mem.Allocator) ![]const u8 {│ │32 _ = allocator; │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 92 const name_to_user_id_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "name->user-id")); │ │ 93 const name_to_user_id = try DB.HashMap(.read_write).init(name_to_user_id_cursor); │ │ 94 │ │ 95 + const email_to_user_id_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, email_to_user_id_key));│ │ 96 + const email_to_user_id = try DB.HashMap(.read_write).init(email_to_user_id_cursor); │ │ 97 + │ │ 98 const conflicts_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, conflicts_key)); │ │ 99 const conflicts = try DB.SortedMap(.read_write).init(conflicts_cursor); │ │100 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │106 │ │107 var event_to_write = event; │ │108 │ │103 - // if this event_id already maps to a user with a different name, │ │104 - // drop the stale name->id entry first │ │109 + // if this event_id already maps to a user with a different name or │ │110 + // email, drop the stale index entries first │ │111 var existing_event_maybe: ?@This() = null; │ │112 const existing_cursor_maybe = try event_id_to_user.getCursor(user_key);│ │113 if (existing_cursor_maybe) |existing_cursor| { │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯