╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ event.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
604 const target_child_cursor = try target.putCursor(kv_pair.hash); 605 const target_child = try DB.HashMap(.read_write).init(target_child_cursor); 606 const parent_child = try DB.HashMap(.read_only).init(kv_pair.value_cursor); 607 const baseline_child = try DB.HashMap(.read_only).init(baseline_value_cursor); 608 try mergeChangedMapEntries(DB, allocator, target_child, parent_child, baseline_child, false); 609 } else if (tag == .sorted_set) { 610 const target_existing_cursor = try target.getCursor(kv_pair.hash) orelse return error.MergeConflict; 611 if (target_existing_cursor.slot().tag != .sorted_set) { 612 return error.UnexpectedTag; 613 } 614 615 const target_child_cursor = try target.putCursor(kv_pair.hash); 616 const target_child = try DB.SortedSet(.read_write).init(target_child_cursor); 617 const parent_child = try DB.SortedSet(.read_only).init(kv_pair.value_cursor); 618 const baseline_child = try DB.SortedSet(.read_only).init(baseline_value_cursor); 619 try mergeChangedSetEntries(DB, allocator, target_child, parent_child, baseline_child); 620 } else if (tag == .sorted_map) { 621 const target_existing_cursor = try target.getCursor(kv_pair.hash) orelse return error.MergeConflict; 622 if (target_existing_cursor.slot().tag != .sorted_map) { 623 return error.UnexpectedTag; 624 } 625 626 const target_child_cursor = try target.putCursor(kv_pair.hash); 627 const target_child = try DB.SortedMap(.read_write).init(target_child_cursor); 628 const parent_child = try DB.SortedMap(.read_only).init(kv_pair.value_cursor); 629 const baseline_child = try DB.SortedMap(.read_only).init(baseline_value_cursor); 630 try mergeChangedSortedMapEntries(DB, allocator, target_child, parent_child, baseline_child); 631 } else if (is_top_level) { 632 // the moment-index is a top-level key in the haxy moment 633 // and is not a map. we don't want to do any merge of this. 634 continue; 635 } else { 636 const target_value_cursor = try target.getCursor(kv_pair.hash) orelse return error.MergeConflict; 637 if (target_value_cursor.slot().tag != baseline_value_cursor.slot().tag) { 638 return error.UnexpectedTag; 639 } 640 if (target_value_cursor.slot().value != baseline_value_cursor.slot().value) { 641 return error.MergeConflict; 642 } 643 644 try target.put(kv_pair.hash, .{ .slot = kv_pair.value_cursor.slot() }); 645 } 646 } 647 } 648 649 // three-way merge of a sorted map keyed by byte strings, like 650 // mergeChangedMapEntries but recursing into sorted-set values. 651 fn mergeChangedSortedMapEntries( 652 comptime DB: type, 653 allocator: std.mem.Allocator, 654 target: DB.SortedMap(.read_write), 655 parent: DB.SortedMap(.read_only), 656 baseline: DB.SortedMap(.read_only), 657 ) !void { 658 var parent_iter = try parent.iterator(); 659 while (try parent_iter.next()) |kv_pair_cursor| { 660 const kv_pair = try kv_pair_cursor.readKeyValuePair(); 661 const key = try kv_pair.key_cursor.readBytesAlloc(allocator, null); 662 defer allocator.free(key); 663 664 const baseline_value_cursor = try baseline.getCursor(key) orelse { 665 if (try target.getCursor(key)) |target_existing_cursor| { 666 // both parents created this key. sets union (they hold 667 // independently added members); anything else conflicts. 668 const parent_tag = kv_pair.value_cursor.slot().tag; 669 if (target_existing_cursor.slot().tag != parent_tag) { 670 return error.UnexpectedTag; 671 } 672 if (parent_tag != .sorted_set) { 673 return error.MergeConflict; 674 } 675 676 const target_child_cursor = try target.putCursor(key); 677 const target_child = try DB.SortedSet(.read_write).init(target_child_cursor); 678 const parent_child = try DB.SortedSet(.read_only).init(kv_pair.value_cursor); 679 var set_iter = try parent_child.iterator(); 680 while (try set_iter.next()) |set_kv_cursor| { 681 const set_kv = try set_kv_cursor.readKeyValuePair(); 682 const set_key = try set_kv.key_cursor.readBytesAlloc(allocator, null); 683 defer allocator.free(set_key); 684 try target_child.put(set_key); 685 } 686 continue; 687 } 688 689 try target.put(key, .{ .slot = kv_pair.value_cursor.slot() }); 690 continue; 691 }; 692 693 const tag = kv_pair.value_cursor.slot().tag; 694 695 if (tag != baseline_value_cursor.slot().tag) { 696 return error.UnexpectedTag; 697 } 698 699 if (kv_pair.value_cursor.slot().value == baseline_value_cursor.slot().value) { 700 continue; 701 } 702 703 if (tag == .sorted_set) { 704 const target_existing_cursor = try target.getCursor(key) orelse return error.MergeConflict; 705 if (target_existing_cursor.slot().tag != .sorted_set) { 706 return error.UnexpectedTag; 707 } 708 709 const target_child_cursor = try target.putCursor(key); 710 const target_child = try DB.SortedSet(.read_write).init(target_child_cursor); 711 const parent_child = try DB.SortedSet(.read_only).init(kv_pair.value_cursor); 712 const baseline_child = try DB.SortedSet(.read_only).init(baseline_value_cursor); 713 try mergeChangedSetEntries(DB, allocator, target_child, parent_child, baseline_child); 714 } else { 715 const target_value_cursor = try target.getCursor(key) orelse return error.MergeConflict; 716 if (target_value_cursor.slot().tag != baseline_value_cursor.slot().tag) { 717 return error.UnexpectedTag; 718 } 719 if (target_value_cursor.slot().value != baseline_value_cursor.slot().value) { 720 return error.MergeConflict; 721 } 722 723 try target.put(key, .{ .slot = kv_pair.value_cursor.slot() }); 724 } 725 } 726 } 727 728 // three-way merge of a sorted set: apply the parent's changes relative to the 729 // baseline. keys have no values, so additions and removals can't conflict. 730 fn mergeChangedSetEntries( 731 comptime DB: type, 732 allocator: std.mem.Allocator, 733 target: DB.SortedSet(.read_write), 734 parent: DB.SortedSet(.read_only), 735 baseline: DB.SortedSet(.read_only), 736 ) !void { 737 var parent_iter = try parent.iterator(); 738 while (try parent_iter.next()) |kv_pair_cursor| { 739 const kv_pair = try kv_pair_cursor.readKeyValuePair(); 740 const key = try kv_pair.key_cursor.readBytesAlloc(allocator, null); 741 defer allocator.free(key); 742 if (!try baseline.contains(key)) try target.put(key); 743 } 744 745 var baseline_iter = try baseline.iterator(); 746 while (try baseline_iter.next()) |kv_pair_cursor| { 747 const kv_pair = try kv_pair_cursor.readKeyValuePair(); 748 const key = try kv_pair.key_cursor.readBytesAlloc(allocator, null); 749 defer allocator.free(key); 750 if (!try parent.contains(key)) _ = try target.remove(key); 751 } 752 } 753 754 fn upsertField( 755 comptime DB: type, 756 comptime hash_kind: hash.HashKind, 757 map: DB.HashMap(.read_write), 758 comptime field_name: []const u8, 759 comptime Field: type, 760 value: Field, 761 ) !void { 762 const key = hash.hashInt(hash_kind, field_name); 763 764 switch (@typeInfo(Field)) { 765 .pointer => |pointer_info| { 766 if (pointer_info.size == .slice and pointer_info.child == u8) { 767 try upsertBytes(DB, hash_kind, map, key, value); 768 } else { 769 @compileError("unsupported upsert field type: " ++ @typeName(Field)); 770 } 771 }, 772 .array => |array_info| { 773 if (array_info.child == u8) { 774 try upsertBytes(DB, hash_kind, map, key, &value); 775 } else { 776 @compileError("unsupported upsert field type: " ++ @typeName(Field)); 777 } 778 }, 779 .int => |int_info| switch (int_info.signedness) { 780 .unsigned => { 781 if (try map.getCursor(key)) |value_cursor| { 782 if (try value_cursor.readUint() == value) { 783 return; 784 } 785 } 786 787 try map.put(key, .{ .uint = value }); 788 }, 789 .signed => { 790 if (try map.getCursor(key)) |value_cursor| { 791 if (try value_cursor.readInt() == value) { 792 return; 793 } 794 } 795 796 try map.put(key, .{ .int = value }); 797 }, 798 }, 799 .bool => { 800 const bytes = if (value) "true" else "false"; 801 if (try map.getCursor(key)) |value_cursor| { 802 var buffer: [5]u8 = undefined; 803 const existing = try value_cursor.readBytesObject(&buffer); 804 if (existing.format_tag) |format_tag| { 805 if (std.mem.eql(u8, &format_tag, "bl") and std.mem.eql(u8, existing.value, bytes)) { 806 return; 807 } 808 } 809 } 810 811 try map.put(key, .{ .bytes_object = .{ .value = bytes, .format_tag = "bl".* } }); 812 }, 813 else => @compileError("unsupported upsert field type: " ++ @typeName(Field)), 814 } 815 } 816 817 fn upsertBytes( 818 comptime DB: type, 819 comptime hash_kind: hash.HashKind, 820 map: DB.HashMap(.read_write), 821 key: hash.HashInt(hash_kind), 822 value: []const u8, 823 ) !void { 824 var existing_cursor_maybe = try map.getCursor(key); 825 if (existing_cursor_maybe) |*existing_cursor| { 826 if (try bytesEqual(DB, existing_cursor, value)) { 827 return; 828 } 829 } 830 831 var value_cursor = try map.putCursor(key); 832 var write_buffer: [1024]u8 = undefined; 833 var writer = try value_cursor.writer(&write_buffer); 834 try writer.interface.writeAll(value); 835 try writer.finish(); 836 } 837 838 fn bytesEqual( 839 comptime DB: type, 840 cursor: *DB.Cursor(.read_only), 841 value: []const u8, 842 ) !bool { 843 var read_buffer: [1024]u8 = undefined; 844 var reader = try cursor.reader(&read_buffer); 845 if (reader.size != value.len) { 846 return false; 847 } 848 849 var chunk_buffer: [1024]u8 = undefined; 850 var offset: usize = 0; 851 while (offset < value.len) { 852 const chunk_len = @min(chunk_buffer.len, value.len - offset); 853 try reader.interface.readSliceAll(chunk_buffer[0..chunk_len]); 854 if (!std.mem.eql(u8, chunk_buffer[0..chunk_len], value[offset .. offset + chunk_len])) { 855 return false; 856 } 857 offset += chunk_len; 858 } 859 860 return true; 861 } 862 863 // 864 // CommitIterator 865 // 866 // differs from xit's ObjectIterator in a few ways: 867 // 868 // 1. iterates parents first, rather than children first 869 // 2. `next` only returns the object id, not the full Object 870 // 3. stores its temporary state in a file on disk to avoid OOMs 871 // 872 873 pub fn CommitIterator(comptime repo_opts: rp.RepoOpts(.xit)) type { 874 return struct { 875 const DB = rp.Repo(.xit, repo_opts).DB; 876 const db_name = "haxy-repo-events.db"; 877 878 repo_dir: std.Io.Dir, 879 db_file: std.Io.File, 880 db: *DB, 881 // map of each object id to its children 882 parent_to_children: DB.HashMap(.read_write), 883 // tracking pending parents is necessary because a child can only 884 // be returned after all of its unconsumed parents have been returned 885 child_to_pending_parents: DB.HashMap(.read_write), 886 // queue of object ids that are ready to be returned from `next` 887 ready_queue: DB.ArrayList(.read_write), 888 ready_queue_index: u64, 889 // the object id at the tip of this branch 890 newest_object_id: ?[hash.byteLen(repo_opts.hash)]u8, 891 // the object id furthest back in the history that hasn't been consumed 892 oldest_object_id: ?[hash.byteLen(repo_opts.hash)]u8, 893 894 pub fn init( 895 state: rp.Repo(.xit, repo_opts).State(.read_only), 896 io: std.Io, 897 allocator: std.mem.Allocator, 898 consumed_object_ids: DB.HashMap(.read_only), 899 ref: rf.Ref, 900 ) !CommitIterator(repo_opts) { 901 const db_file = try state.core.repo_dir.createFile(io, db_name, .{ .truncate = true, .lock = .exclusive, .read = true }); 902 errdefer { 903 db_file.close(io); 904 state.core.repo_dir.deleteFile(io, db_name) catch {}; 905 } 906 907 const buffer_ptr = try allocator.create(std.Io.Writer.Allocating); 908 errdefer allocator.destroy(buffer_ptr); 909 910 buffer_ptr.* = std.Io.Writer.Allocating.init(allocator); 911 errdefer buffer_ptr.deinit(); 912 913 const db_ptr = try allocator.create(DB); 914 errdefer allocator.destroy(db_ptr); 915 db_ptr.* = try DB.init(.{ .io = io, .file = db_file, .buffer = buffer_ptr }); 916 917 const map = try DB.HashMap(.read_write).init(db_ptr.rootCursor()); 918 919 const parent_to_children_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "parent->children")); 920 const parent_to_children = try DB.HashMap(.read_write).init(parent_to_children_cursor); 921 922 const child_to_pending_parents_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "child->pending-parents")); 923 const child_to_pending_parents = try DB.HashMap(.read_write).init(child_to_pending_parents_cursor); 924 925 const ready_queue_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "ready-queue")); 926 const ready_queue = try DB.ArrayList(.read_write).init(ready_queue_cursor); 927 928 var self = CommitIterator(repo_opts){ 929 .repo_dir = state.core.repo_dir, 930 .db_file = db_file, 931 .db = db_ptr, 932 .parent_to_children = parent_to_children, 933 .child_to_pending_parents = child_to_pending_parents, 934 .ready_queue = ready_queue, 935 .ready_queue_index = 0, 936 .newest_object_id = null, 937 .oldest_object_id = null, 938 }; 939 errdefer self.deinit(io, allocator); 940 941 try self.collect(state, io, allocator, consumed_object_ids, ref); 942 943 return self; 944 } 945 946 pub fn deinit(self: *CommitIterator(repo_opts), io: std.Io, allocator: std.mem.Allocator) void { 947 self.db_file.close(io); 948 self.db.core.memory.buffer.deinit(); 949 allocator.destroy(self.db.core.memory.buffer); 950 self.repo_dir.deleteFile(io, db_name) catch {}; 951 allocator.destroy(self.db); 952 } 953 954 pub fn next(self: *CommitIterator(repo_opts)) !?[hash.byteLen(repo_opts.hash)]u8 { 955 if (self.ready_queue_index >= try self.ready_queue.count()) return null; 956 957 const oid = try readOidFromList(self.ready_queue.readOnly(), self.ready_queue_index); 958 self.ready_queue_index += 1; 959 960 try self.enqueueChildren(&oid); 961 return oid; 962 } 963 964 fn collect( 965 self: *CommitIterator(repo_opts), 966 state: rp.Repo(.xit, repo_opts).State(.read_only), 967 io: std.Io, 968 allocator: std.mem.Allocator, 969 consumed_object_ids: DB.HashMap(.read_only), 970 ref: rf.Ref, 971 ) !void { 972 const map = try DB.HashMap(.read_write).init(self.db.rootCursor()); 973 974 const walk_queue_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "walk-queue")); 975 const walk_queue = try DB.ArrayList(.read_write).init(walk_queue_cursor); 976 var walk_queue_index: u64 = 0; 977 978 const seen_object_ids_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "seen-object-ids")); 979 const seen_object_ids = try DB.HashSet(.read_write).init(seen_object_ids_cursor); 980 981 const head_oid_hex = (try rf.readRecur(.xit, repo_opts, state, io, .{ .ref = ref })) orelse return error.OidNotFound; 982 var head_oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; 983 _ = try std.fmt.hexToBytes(&head_oid, &head_oid_hex); 984 try walk_queue.append(.{ .bytes = &head_oid }); 985 986 while (walk_queue_index < try walk_queue.count()) { 987 const oid = try readOidFromList(walk_queue.readOnly(), walk_queue_index); 988 const oid_int = hash.bytesToInt(repo_opts.hash, &oid); 989 990 walk_queue_index += 1; 991 992 // if we've seen this object id, skip it 993 if (null != try seen_object_ids.getCursor(oid_int)) { 994 continue; 995 } 996 997 try seen_object_ids.put(oid_int, .{ .bytes = &oid }); 998 999 var commit_object = try obj.Object(.xit, repo_opts).init(state, io, allocator, &std.fmt.bytesToHex(oid, .lower)); 1000 defer commit_object.deinit(); 1001 1002 // if this object id has already been consumed, skip it 1003 if (null != try consumed_object_ids.getCursor(oid_int)) { 1004 continue; 1005 } 1006 1007 if (self.newest_object_id == null) self.newest_object_id = oid; 1008 self.oldest_object_id = oid; 1009 1010 const parent_oids = commit_object.content.commit.metadata.parent_oids orelse return error.ParentOidsNotFound; 1011 var pending_parent_count: u64 = 0; 1012 for (parent_oids) |*parent_oid| { 1013 var parent_oid_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; 1014 _ = try std.fmt.hexToBytes(&parent_oid_bytes, parent_oid); 1015 const parent_oid_int = hash.bytesToInt(repo_opts.hash, &parent_oid_bytes); 1016 1017 // if this object id has already been consumed, skip it 1018 if (null != try consumed_object_ids.getCursor(parent_oid_int)) { 1019 continue; 1020 } 1021 1022 // if this object id hasn't already been seen, add it to the walk queue 1023 if (null == try seen_object_ids.getCursor(parent_oid_int)) { 1024 try walk_queue.append(.{ .bytes = &parent_oid_bytes }); 1025 } 1026 1027 const children_cursor = try self.parent_to_children.putCursor(parent_oid_int); 1028 const children = try DB.HashSet(.read_write).init(children_cursor); 1029 try children.put(oid_int, .{ .bytes = &oid }); 1030 pending_parent_count += 1; 1031 } 1032 1033 if (pending_parent_count == 0) { 1034 try self.ready_queue.append(.{ .bytes = &oid }); 1035 } else { 1036 try self.child_to_pending_parents.put(oid_int, .{ .uint = pending_parent_count }); 1037 } 1038 } 1039 } 1040 1041 fn enqueueChildren(self: *CommitIterator(repo_opts), oid: *const [hash.byteLen(repo_opts.hash)]u8) !void { 1042 const oid_int = hash.bytesToInt(repo_opts.hash, oid); 1043 const children_cursor = try self.parent_to_children.getCursor(oid_int) orelse return; 1044 const children = try DB.HashSet(.read_only).init(children_cursor); 1045 var children_iter = try children.iterator(); 1046 1047 while (try children_iter.next()) |*child_cursor| { 1048 const kv_pair = try child_cursor.readKeyValuePair(); 1049 const child_oid_int = kv_pair.hash; 1050 const pending_parent_cursor = try self.child_to_pending_parents.getCursor(child_oid_int) orelse return error.CursorNotFound; 1051 const pending_parent_count = try pending_parent_cursor.readUint(); 1052 1053 if (pending_parent_count <= 1) { 1054 _ = try self.child_to_pending_parents.remove(child_oid_int); 1055 const child_oid = hash.intToBytes(hash.HashInt(repo_opts.hash), child_oid_int); 1056 try self.ready_queue.append(.{ .bytes = &child_oid }); 1057 } else { 1058 try self.child_to_pending_parents.put(child_oid_int, .{ .uint = pending_parent_count - 1 }); 1059 } 1060 } 1061 } 1062 1063 fn readOidFromList(list: DB.ArrayList(.read_only), index: u64) ![hash.byteLen(repo_opts.hash)]u8 { 1064 const oid_cursor = try list.getCursor(index) orelse return error.CursorNotFound; 1065 var oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; 1066 _ = try oid_cursor.readBytes(&oid); 1067 return oid; 1068 } 1069 }; 1070 } 1071