╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │807 const moment_index = try moment_index_cursor.readUint(); │ │808 │ │809 try haxy.slice(moment_index + 1); │ │789 - try moment.put(hash.hashInt(repo_opts.hash, "haxy-last-object-id"), .{ .bytes = &head_oid });│ │810 + try moment.put(hash.hashInt(repo_opts.hash, last_object_id_key), .{ .bytes = &head_oid }); │ │811 return true; │ │812 } │ │813 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1022 if (existing.patch_oid.len != patch_oid.len) return error.InvalidPatch; │ │1023 @memcpy(&patch_oid, existing.patch_oid); │ │1024 } else { │ │1004 - var base_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; │ │1005 - if (event.base_oid.len != base_oid.len) return error.InvalidPatch; │ │1006 - @memcpy(&base_oid, event.base_oid); │ │1007 - var base_oid_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │1008 - _ = try std.fmt.hexToBytes(&base_oid_bytes, &base_oid); │ │1009 - if (!std.mem.eql(u8, &base_oid, &std.fmt.bytesToHex(base_oid_bytes, .lower))) return error.InvalidPatch; │ │1010 - const derived_parent_oids = [_][hash.hexLen(repo_opts.hash)]u8{base_oid}; │ │1025 const author = try commitIdentity(commit_object.content.commit.metadata.author orelse return error.AuthorNotFound);│ │1026 const committer = try commitIdentity(commit_object.content.commit.metadata.committer orelse author); │ │1013 - patch_oid = try obj.writeCommitWithoutRef(repo_kind, repo_opts, state, io, allocator, .{ │ │1014 - .author = author, │ │1015 - .committer = committer, │ │1016 - .message = event.message, │ │1017 - .parent_oids = &derived_parent_oids, │ │1018 - .timestamp = commit_object.content.commit.metadata.timestamp, │ │1019 - }, &trees.head); │ │1027 + patch_oid = try PatchRev.writeSquashCommit( │ │1028 + repo_kind, │ │1029 + repo_opts, │ │1030 + state, │ │1031 + io, │ │1032 + allocator, │ │1033 + event, │ │1034 + &trees.head, │ │1035 + author, │ │1036 + committer, │ │1037 + commit_object.content.commit.metadata.timestamp, │ │1038 + ); │ │1039 } │ │1040 │ │1041 break :blk .{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1069 } │ │1070 │ │1071 if (last_object_id_maybe) |*last_object_id| { │ │1053 - try moment.put(hash.hashInt(repo_opts.hash, "haxy-last-object-id"), .{ .bytes = last_object_id });│ │1072 + try moment.put(hash.hashInt(repo_opts.hash, last_object_id_key), .{ .bytes = last_object_id }); │ │1073 } │ │1074 │ │1075 return true; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1415 return currentMomentFromDb(repo_opts.hash, &repo.core.db); │ │1416 } │ │1417 │ │1399 -// the oid of the last event commit consumed into `db`, or null before any │ │1400 -// consume completed │ │1401 -fn lastConsumedOid( │ │1418 +pub fn currentMomentFromDb( │ │1419 comptime hash_kind: hash.HashKind, │ │1420 db: *EventDB(hash_kind), │ │1404 -) !?[hash.byteLen(hash_kind)]u8 { │ │1421 +) !EventDB(hash_kind).HashMap(.read_only) { │ │1422 const DB = EventDB(hash_kind); │ │1423 const history = try DB.ArrayList(.read_only).init(db.rootCursor().readOnly()); │ │1407 - const moment_cursor = try history.getCursor(-1) orelse return null; │ │1424 + const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; │ │1425 const moment = try DB.HashMap(.read_only).init(moment_cursor); │ │1409 - const last_object_id_cursor = try moment.getCursor(hash.hashInt(hash_kind, "haxy-last-object-id")) orelse return null; │ │1410 - var last_object_id: [hash.byteLen(hash_kind)]u8 = undefined; │ │1411 - _ = try last_object_id_cursor.readBytes(&last_object_id); │ │1412 - return last_object_id; │ │1426 + return try currentMomentFromRepoMoment(hash_kind, moment); │ │1427 } │ │1428 │ │1415 -pub fn currentMomentFromDb( │ │1429 +pub fn currentMomentFromRepoMoment( │ │1430 comptime hash_kind: hash.HashKind, │ │1417 - db: *EventDB(hash_kind), │ │1431 + moment: EventDB(hash_kind).HashMap(.read_only), │ │1432 ) !EventDB(hash_kind).HashMap(.read_only) { │ │1433 const DB = EventDB(hash_kind); │ │1420 - const last_object_id = (try lastConsumedOid(hash_kind, db)) orelse return error.NotFound; │ │1421 - const history = try DB.ArrayList(.read_only).init(db.rootCursor().readOnly()); │ │1422 - const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; │ │1423 - const moment = try DB.HashMap(.read_only).init(moment_cursor); │ │1424 - const haxy_cursor = try moment.getCursor(hash.hashInt(hash_kind, "haxy")) orelse return error.NotFound; │ │1434 + const last_object_id_cursor = try moment.getCursor(hash.hashInt(hash_kind, last_object_id_key)) orelse return error.NotFound;│ │1435 + var last_object_id: [hash.byteLen(hash_kind)]u8 = undefined; │ │1436 + _ = try last_object_id_cursor.readBytes(&last_object_id); │ │1437 + const haxy_cursor = try moment.getCursor(hash.hashInt(hash_kind, materialized_key)) orelse return error.NotFound; │ │1438 const haxy = try DB.ArrayList(.read_only).init(haxy_cursor); │ │1439 const haxy_moments_cursor = try haxy.getCursor(-1) orelse return error.NotFound; │ │1440 const haxy_moments = try DB.HashMap(.read_only).init(haxy_moments_cursor); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │1531 │ │1532 return .{ .owner = owner, .name = name }; │ │1533 } │ │1521 - │ │1522 -pub const ResolveRepoOptions = struct { │ │1523 - create: ?CreateOptions = null, │ │1534 │ │1525 - pub const CreateOptions = struct { │ │1526 - read_access: Repo.Access = .private, │ │1527 - }; │ │1535 +pub const CreateRepoOptions = struct { │ │1536 + read_access: Repo.Access = .private, │ │1537 }; │ │1538 │ │1539 // resolve a pushed `<owner>/<repo>` to the hex event id that names its on-disk│ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1544 admin_repo_path: []const u8, │ │1545 owner_name: []const u8, │ │1546 repo_name: []const u8, │ │1538 - options: ResolveRepoOptions, │ │1547 + create_options_maybe: ?CreateRepoOptions, │ │1548 ) !?[event_id_size * 2]u8 { │ │1549 var repo = rp.Repo(.xit, admin_repo_opts).open(io, allocator, .{ .path = admin_repo_path }) catch |err| switch (err) {│ │1550 error.RepoNotFound => return null, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │1563 return std.fmt.bytesToHex(found.event_id, .lower); │ │1564 } │ │1565 │ │1557 - const create_options = options.create orelse return null; │ │1566 + const create_options = create_options_maybe orelse return null; │ │1567 │ │1568 // the new repo is owned by the named user, read from the name index; an│ │1569 // unknown owner can't own a repo, so the push is rejected │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1598 // reading from xitdb │ │1599 // │ │1600 │ │1592 -// a struct field's fields share its container's map, so their names must not │ │1593 -// collide with the container's or two fields would silently share a key │ │1594 -fn validateFlattenedFields(comptime T: type) void { │ │1595 - comptime { │ │1596 - for (std.meta.fields(T)) |field| { │ │1597 - if (@typeInfo(field.type) != .@"struct") continue; │ │1598 - for (std.meta.fields(field.type)) |nested| { │ │1599 - if (@hasField(T, nested.name)) │ │1600 - @compileError("flattened field name collision in " ++ @typeName(T) ++ ": " ++ nested.name);│ │1601 - } │ │1602 - } │ │1603 - } │ │1604 -} │ │1605 - │ │1601 pub fn read( │ │1602 comptime T: type, │ │1603 comptime DB: type, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────╮ │1609 │ │1610 switch (@typeInfo(T)) { │ │1611 .@"struct" => |struct_info| { │ │1617 - comptime validateFlattenedFields(T); │ │1612 inline for (struct_info.fields) |field| {│ │1613 switch (@typeInfo(field.type)) { │ │1614 .pointer => |pointer_info| { │ ╰───────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1649 if (value.len != bytes.len) return error.InvalidByteArrayLength; │ │1650 @field(event, field.name) = bytes; │ │1651 }, │ │1652 + .@"struct" => @field(event, field.name) = try read( │ │1653 + optional_info.child, │ │1654 + DB, │ │1655 + hash_kind, │ │1656 + arena, │ │1657 + try DB.HashMap(.read_only).init(cursor), │ │1658 + ), │ │1659 else => @compileError("unsupported read field type: " ++ @typeName(field.type)),│ │1660 } │ │1661 } else { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯