╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ event.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
976 .unsigned => { 977 if (try map.getCursor(key)) |value_cursor| { 978 if (try value_cursor.readUint() == value) { 979 return; 980 } 981 } 982 983 try map.put(key, .{ .uint = value }); 984 }, 985 .signed => { 986 if (try map.getCursor(key)) |value_cursor| { 987 if (try value_cursor.readInt() == value) { 988 return; 989 } 990 } 991 992 try map.put(key, .{ .int = value }); 993 }, 994 }, 995 .bool => { 996 const bytes = if (value) "true" else "false"; 997 if (try map.getCursor(key)) |value_cursor| { 998 var buffer: [5]u8 = undefined; 999 const existing = try value_cursor.readBytesObject(&buffer); 1000 if (existing.format_tag) |format_tag| { 1001 if (std.mem.eql(u8, &format_tag, "bl") and std.mem.eql(u8, existing.value, bytes)) { 1002 return; 1003 } 1004 } 1005 } 1006 1007 try map.put(key, .{ .bytes_object = .{ .value = bytes, .format_tag = "bl".* } }); 1008 }, 1009 .@"enum" => try upsertBytes(DB, hash_kind, map, key, @tagName(value)), 1010 else => @compileError("unsupported upsert field type: " ++ @typeName(Field)), 1011 } 1012 } 1013 1014 fn upsertBytes( 1015 comptime DB: type, 1016 comptime hash_kind: hash.HashKind, 1017 map: DB.HashMap(.read_write), 1018 key: hash.HashInt(hash_kind), 1019 value: []const u8, 1020 ) !void { 1021 var existing_cursor_maybe = try map.getCursor(key); 1022 if (existing_cursor_maybe) |*existing_cursor| { 1023 if (try bytesEqual(DB, existing_cursor, value)) { 1024 return; 1025 } 1026 } 1027 1028 var value_cursor = try map.putCursor(key); 1029 var write_buffer: [1024]u8 = undefined; 1030 var writer = try value_cursor.writer(&write_buffer); 1031 try writer.interface.writeAll(value); 1032 try writer.finish(); 1033 } 1034 1035 fn bytesEqual( 1036 comptime DB: type, 1037 cursor: *DB.Cursor(.read_only), 1038 value: []const u8, 1039 ) !bool { 1040 var read_buffer: [1024]u8 = undefined; 1041 var reader = try cursor.reader(&read_buffer); 1042 if (reader.size != value.len) { 1043 return false; 1044 } 1045 1046 var chunk_buffer: [1024]u8 = undefined; 1047 var offset: usize = 0; 1048 while (offset < value.len) { 1049 const chunk_len = @min(chunk_buffer.len, value.len - offset); 1050 try reader.interface.readSliceAll(chunk_buffer[0..chunk_len]); 1051 if (!std.mem.eql(u8, chunk_buffer[0..chunk_len], value[offset .. offset + chunk_len])) { 1052 return false; 1053 } 1054 offset += chunk_len; 1055 } 1056 1057 return true; 1058 } 1059 1060 // 1061 // CommitIterator 1062 // 1063 // differs from xit's ObjectIterator in a few ways: 1064 // 1065 // 1. iterates parents first, rather than children first 1066 // 2. `next` only returns the object id, not the full Object 1067 // 3. stores its temporary state in a file on disk to avoid OOMs 1068 // 1069 1070 pub fn CommitIterator(comptime repo_kind: rp.RepoKind, comptime repo_opts: rp.RepoOpts(repo_kind)) type { 1071 return struct { 1072 const DB = EventDB(repo_opts.hash); 1073 const db_name = "haxy-repo-events.db"; 1074 1075 repo_dir: std.Io.Dir, 1076 db_file: std.Io.File, 1077 db: *DB, 1078 // map of each object id to its children 1079 parent_to_children: DB.HashMap(.read_write), 1080 // tracking pending parents is necessary because a child can only 1081 // be returned after all of its unconsumed parents have been returned 1082 child_to_pending_parents: DB.HashMap(.read_write), 1083 // queue of object ids that are ready to be returned from `next` 1084 ready_queue: DB.ArrayList(.read_write), 1085 ready_queue_index: u64, 1086 // the object id at the tip of this branch 1087 newest_object_id: ?[hash.byteLen(repo_opts.hash)]u8, 1088 // the object id furthest back in the history that hasn't been consumed 1089 oldest_object_id: ?[hash.byteLen(repo_opts.hash)]u8, 1090 1091 pub fn init( 1092 state: rp.Repo(repo_kind, repo_opts).State(.read_only), 1093 io: std.Io, 1094 allocator: std.mem.Allocator, 1095 consumed_object_ids: DB.HashMap(.read_only), 1096 ref: rf.Ref, 1097 ) !CommitIterator(repo_kind, repo_opts) { 1098 const db_file = try state.core.repo_dir.createFile(io, db_name, .{ .truncate = true, .lock = .exclusive, .read = true }); 1099 errdefer { 1100 db_file.close(io); 1101 state.core.repo_dir.deleteFile(io, db_name) catch {}; 1102 } 1103 1104 const buffer_ptr = try allocator.create(std.Io.Writer.Allocating); 1105 errdefer allocator.destroy(buffer_ptr); 1106 1107 buffer_ptr.* = std.Io.Writer.Allocating.init(allocator); 1108 errdefer buffer_ptr.deinit(); 1109 1110 const db_ptr = try allocator.create(DB); 1111 errdefer allocator.destroy(db_ptr); 1112 db_ptr.* = try DB.init(.{ .io = io, .file = db_file, .buffer = buffer_ptr }); 1113 1114 const map = try DB.HashMap(.read_write).init(db_ptr.rootCursor()); 1115 1116 const parent_to_children_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "parent->children")); 1117 const parent_to_children = try DB.HashMap(.read_write).init(parent_to_children_cursor); 1118 1119 const child_to_pending_parents_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "child->pending-parents")); 1120 const child_to_pending_parents = try DB.HashMap(.read_write).init(child_to_pending_parents_cursor); 1121 1122 const ready_queue_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "ready-queue")); 1123 const ready_queue = try DB.ArrayList(.read_write).init(ready_queue_cursor); 1124 1125 var self = CommitIterator(repo_kind, repo_opts){ 1126 .repo_dir = state.core.repo_dir, 1127 .db_file = db_file, 1128 .db = db_ptr, 1129 .parent_to_children = parent_to_children, 1130 .child_to_pending_parents = child_to_pending_parents, 1131 .ready_queue = ready_queue, 1132 .ready_queue_index = 0, 1133 .newest_object_id = null, 1134 .oldest_object_id = null, 1135 }; 1136 errdefer self.deinit(io, allocator); 1137 1138 try self.collect(state, io, allocator, consumed_object_ids, ref); 1139 1140 return self; 1141 } 1142 1143 pub fn deinit(self: *CommitIterator(repo_kind, repo_opts), io: std.Io, allocator: std.mem.Allocator) void { 1144 self.db_file.close(io); 1145 self.db.core.memory.buffer.deinit(); 1146 allocator.destroy(self.db.core.memory.buffer); 1147 self.repo_dir.deleteFile(io, db_name) catch {}; 1148 allocator.destroy(self.db); 1149 } 1150 1151 pub fn next(self: *CommitIterator(repo_kind, repo_opts)) !?[hash.byteLen(repo_opts.hash)]u8 { 1152 if (self.ready_queue_index >= try self.ready_queue.count()) return null; 1153 1154 const oid = try readOidFromList(self.ready_queue.readOnly(), self.ready_queue_index); 1155 self.ready_queue_index += 1; 1156 1157 try self.enqueueChildren(&oid); 1158 return oid; 1159 } 1160 1161 fn collect( 1162 self: *CommitIterator(repo_kind, repo_opts), 1163 state: rp.Repo(repo_kind, repo_opts).State(.read_only), 1164 io: std.Io, 1165 allocator: std.mem.Allocator, 1166 consumed_object_ids: DB.HashMap(.read_only), 1167 ref: rf.Ref, 1168 ) !void { 1169 const map = try DB.HashMap(.read_write).init(self.db.rootCursor()); 1170 1171 const walk_queue_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "walk-queue")); 1172 const walk_queue = try DB.ArrayList(.read_write).init(walk_queue_cursor); 1173 var walk_queue_index: u64 = 0; 1174 1175 const seen_object_ids_cursor = try map.putCursor(hash.hashInt(repo_opts.hash, "seen-object-ids")); 1176 const seen_object_ids = try DB.HashSet(.read_write).init(seen_object_ids_cursor); 1177 1178 const head_oid_hex = (try rf.readRecur(repo_kind, repo_opts, state, io, .{ .ref = ref })) orelse return error.OidNotFound; 1179 var head_oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; 1180 _ = try std.fmt.hexToBytes(&head_oid, &head_oid_hex); 1181 try walk_queue.append(.{ .bytes = &head_oid }); 1182 1183 while (walk_queue_index < try walk_queue.count()) { 1184 const oid = try readOidFromList(walk_queue.readOnly(), walk_queue_index); 1185 const oid_int = hash.bytesToInt(repo_opts.hash, &oid); 1186 1187 walk_queue_index += 1; 1188 1189 // if we've seen this object id, skip it 1190 if (null != try seen_object_ids.getCursor(oid_int)) { 1191 continue; 1192 } 1193 1194 try seen_object_ids.put(oid_int, .{ .bytes = &oid }); 1195 1196 var commit_object = try obj.Object(repo_kind, repo_opts).init(state, io, allocator, &std.fmt.bytesToHex(oid, .lower)); 1197 defer commit_object.deinit(); 1198 1199 // if this object id has already been consumed, skip it 1200 if (null != try consumed_object_ids.getCursor(oid_int)) { 1201 continue; 1202 } 1203 1204 if (self.newest_object_id == null) self.newest_object_id = oid; 1205 self.oldest_object_id = oid; 1206 1207 const parent_oids = commit_object.content.commit.metadata.parent_oids orelse return error.ParentOidsNotFound; 1208 var pending_parent_count: u64 = 0; 1209 for (parent_oids) |*parent_oid| { 1210 var parent_oid_bytes: [hash.byteLen(repo_opts.hash)]u8 = undefined; 1211 _ = try std.fmt.hexToBytes(&parent_oid_bytes, parent_oid); 1212 const parent_oid_int = hash.bytesToInt(repo_opts.hash, &parent_oid_bytes); 1213 1214 // if this object id has already been consumed, skip it 1215 if (null != try consumed_object_ids.getCursor(parent_oid_int)) { 1216 continue; 1217 } 1218 1219 // if this object id hasn't already been seen, add it to the walk queue 1220 if (null == try seen_object_ids.getCursor(parent_oid_int)) { 1221 try walk_queue.append(.{ .bytes = &parent_oid_bytes }); 1222 } 1223 1224 const children_cursor = try self.parent_to_children.putCursor(parent_oid_int); 1225 const children = try DB.HashSet(.read_write).init(children_cursor); 1226 try children.put(oid_int, .{ .bytes = &oid }); 1227 pending_parent_count += 1; 1228 } 1229 1230 if (pending_parent_count == 0) { 1231 try self.ready_queue.append(.{ .bytes = &oid }); 1232 } else { 1233 try self.child_to_pending_parents.put(oid_int, .{ .uint = pending_parent_count }); 1234 } 1235 } 1236 } 1237 1238 fn enqueueChildren(self: *CommitIterator(repo_kind, repo_opts), oid: *const [hash.byteLen(repo_opts.hash)]u8) !void { 1239 const oid_int = hash.bytesToInt(repo_opts.hash, oid); 1240 const children_cursor = try self.parent_to_children.getCursor(oid_int) orelse return; 1241 const children = try DB.HashSet(.read_only).init(children_cursor); 1242 var children_iter = try children.iterator(); 1243 1244 while (try children_iter.next()) |*child_cursor| { 1245 const kv_pair = try child_cursor.readKeyValuePair(); 1246 const child_oid_int = kv_pair.hash; 1247 const pending_parent_cursor = try self.child_to_pending_parents.getCursor(child_oid_int) orelse return error.CursorNotFound; 1248 const pending_parent_count = try pending_parent_cursor.readUint(); 1249 1250 if (pending_parent_count <= 1) { 1251 _ = try self.child_to_pending_parents.remove(child_oid_int); 1252 const child_oid = hash.intToBytes(hash.HashInt(repo_opts.hash), child_oid_int); 1253 try self.ready_queue.append(.{ .bytes = &child_oid }); 1254 } else { 1255 try self.child_to_pending_parents.put(child_oid_int, .{ .uint = pending_parent_count - 1 }); 1256 } 1257 } 1258 } 1259 1260 fn readOidFromList(list: DB.ArrayList(.read_only), index: u64) ![hash.byteLen(repo_opts.hash)]u8 { 1261 const oid_cursor = try list.getCursor(index) orelse return error.CursorNotFound; 1262 var oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; 1263 _ = try oid_cursor.readBytes(&oid); 1264 return oid; 1265 } 1266 }; 1267 } 1268