| ... | ... | @@ -761,7 +761,7 @@ pub const Repository = struct { |
| 761 | 761 | for (r.pack_content.values()) |v| nfs.munmap(v); |
| 762 | 762 | r.pack_content.deinit(r.gpa); |
| 763 | 763 | r.commits.deinit(r.gpa); |
| 764 | | for (r.trees.values()) |*v| v.children.deinit(r.gpa); |
| 764 | for (r.trees.values()) |*v| r.gpa.free(v.children); |
| 765 | 765 | r.trees.deinit(r.gpa); |
| 766 | 766 | r.tags.deinit(r.gpa); |
| 767 | 767 | } |
| ... | ... | @@ -1097,8 +1097,7 @@ pub const Repository = struct { |
| 1097 | 1097 | } |
| 1098 | 1098 | if (try r.getObject(arena, id.id)) |obj| { |
| 1099 | 1099 | if (obj.type == .tree) { |
| 1100 | | const MAL = MultiArrayList(Tree.Object); |
| 1101 | | var children: MAL = .empty; |
| 1100 | var children: std.ArrayList(Tree.Object) = .empty; |
| 1102 | 1101 | errdefer children.deinit(r.gpa); |
| 1103 | 1102 | try children.ensureUnusedCapacity(r.gpa, 33); |
| 1104 | 1103 | var i: usize = 0; |
| ... | ... | @@ -1133,24 +1132,9 @@ pub const Repository = struct { |
| 1133 | 1132 | }); |
| 1134 | 1133 | } |
| 1135 | 1134 | |
| 1136 | | const S = struct { |
| 1137 | | list: *MAL, |
| 1138 | | |
| 1139 | | pub fn lessThan(s: @This(), a: usize, b: usize) bool { |
| 1140 | | const items = s.list.items.name; |
| 1141 | | return std.mem.order(u8, items[a], items[b]) == .lt; |
| 1142 | | } |
| 1143 | | pub fn swap(s: @This(), a: usize, b: usize) void { |
| 1144 | | // Remove after updating to Zig 0.17. Ref: https://codeberg.org/ziglang/zig/pulls/32016 |
| 1145 | | inline for (@typeInfo(Tree.Object).@"struct".fields) |field| { |
| 1146 | | const its = @field(s.list.items, field.name); |
| 1147 | | std.mem.swap(@FieldType(Tree.Object, field.name), &its[a], &its[b]); |
| 1148 | | } |
| 1149 | | } |
| 1150 | | }; |
| 1151 | | if (children.len() > 1000) std.mem.sortContext(0, children.len(), S{ .list = &children }); |
| 1152 | | |
| 1153 | | const tree: Tree = .{ .children = children }; |
| 1135 | const children_slice = try children.toOwnedSlice(r.gpa); |
| 1136 | errdefer r.gpa.free(children_slice); |
| 1137 | const tree: Tree = .{ .children = children_slice }; |
| 1154 | 1138 | try r.trees.put(r.gpa, id.id, tree); |
| 1155 | 1139 | return .{ id, tree }; |
| 1156 | 1140 | } |
| ... | ... | @@ -1263,12 +1247,12 @@ pub const Repository = struct { |
| 1263 | 1247 | const base = base_idx.reify(r); |
| 1264 | 1248 | const base_tree_id = (try traverseTo(r, arena, base.tree, dir_path)).?; |
| 1265 | 1249 | const base_tree = try r.getTreeA(arena, base_tree_id.id); |
| 1266 | | const total = base_tree.children.len(); |
| 1250 | const total = base_tree.children.len; |
| 1267 | 1251 | |
| 1268 | 1252 | var found: usize = 0; |
| 1269 | 1253 | var result: std.StringArrayHashMapUnmanaged(CommitId) = .empty; |
| 1270 | 1254 | defer result.deinit(r.gpa); |
| 1271 | | for (base_tree.children.items.name) |name| try result.put(r.gpa, name, undefined); |
| 1255 | for (base_tree.children) |obj| try result.put(r.gpa, obj.name, undefined); |
| 1272 | 1256 | |
| 1273 | 1257 | var set: std.bit_set.DynamicBitSetUnmanaged = try .initEmpty(r.gpa, total); |
| 1274 | 1258 | defer set.deinit(r.gpa); |
| ... | ... | @@ -1301,7 +1285,7 @@ pub const Repository = struct { |
| 1301 | 1285 | const tree = try r.getTreeA(arena, tree_id.id); |
| 1302 | 1286 | for (result.keys(), 0..) |k, i| { |
| 1303 | 1287 | if (set.isSet(i)) continue; |
| 1304 | | const new = tree.get(k); |
| 1288 | const new = tree.get(k, base_tree.children[i].mode.type); |
| 1305 | 1289 | if (new == null) { |
| 1306 | 1290 | found += 1; |
| 1307 | 1291 | result.putAssumeCapacity(k, commit_id_prev); |
| ... | ... | @@ -1309,7 +1293,7 @@ pub const Repository = struct { |
| 1309 | 1293 | // std.log.debug("found [{d}/{d}] objects after searching {d} commits", .{ found, total, searched }); |
| 1310 | 1294 | continue; |
| 1311 | 1295 | } |
| 1312 | | if (!std.mem.eql(u8, new.?.id.erase(), base_tree.children.items.id[i].erase())) { |
| 1296 | if (!std.mem.eql(u8, new.?.id.erase(), base_tree.children[i].id.erase())) { |
| 1313 | 1297 | found += 1; |
| 1314 | 1298 | result.putAssumeCapacity(k, commit_id_prev); |
| 1315 | 1299 | set.set(i); |
| ... | ... | @@ -1393,7 +1377,7 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di |
| 1393 | 1377 | var iter = std.mem.splitScalar(u8, dir_path, '/'); |
| 1394 | 1378 | while (iter.next()) |segment| { |
| 1395 | 1379 | const tree = try r.getTreeA(arena, id.id); |
| 1396 | | const o = tree.get(segment) orelse return null; |
| 1380 | const o = tree.get(segment, .directory) orelse return null; |
| 1397 | 1381 | if (o.id != .tree) return null; |
| 1398 | 1382 | id = o.id.tree; |
| 1399 | 1383 | } |
| ... | ... | @@ -1401,31 +1385,23 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di |
| 1401 | 1385 | } |
| 1402 | 1386 | |
| 1403 | 1387 | pub const Tree = struct { |
| 1404 | | children: MultiArrayList(Object), |
| 1388 | children: []const Object, |
| 1405 | 1389 | |
| 1406 | | pub fn get(self: Tree, name: string) ?Object { |
| 1407 | | if (self.children.len() <= 1000) { |
| 1408 | | for (self.children.items.name, 0..) |a_name, i| { |
| 1409 | | if (std.mem.eql(u8, a_name, name)) { |
| 1410 | | return self.children.get(i); |
| 1411 | | } |
| 1412 | | } |
| 1413 | | return null; |
| 1414 | | } |
| 1415 | | const i = std.sort.binarySearch([]const u8, self.children.items.name, name, extras.compareFnSlice(u8)) orelse return null; |
| 1416 | | return self.children.get(i); |
| 1390 | pub fn get(self: Tree, name: string, hint: Object.Type) ?Object { |
| 1391 | const i = std.sort.binarySearch(Object, self.children, .{ name, hint }, Object.search) orelse return null; |
| 1392 | return self.children[i]; |
| 1417 | 1393 | } |
| 1418 | 1394 | |
| 1419 | | pub fn getBlob(self: Tree, name: string) ?Object { |
| 1420 | | const o = self.get(name) orelse return null; |
| 1395 | pub fn getBlob(self: Tree, name: string, hint: Object.Type) ?Object { |
| 1396 | const o = self.get(name, hint) orelse return null; |
| 1421 | 1397 | if (o.id != .blob) return null; |
| 1422 | 1398 | return o; |
| 1423 | 1399 | } |
| 1424 | 1400 | |
| 1425 | 1401 | pub fn find(self: Tree, name: string) ?Object { |
| 1426 | | for (self.children.items.name, 0..) |item, i| { |
| 1427 | | if (std.ascii.eqlIgnoreCase(item, name)) { |
| 1428 | | return self.children.get(i); |
| 1402 | for (self.children, 0..) |item, i| { |
| 1403 | if (std.ascii.eqlIgnoreCase(item.name, name)) { |
| 1404 | return self.children[i]; |
| 1429 | 1405 | } |
| 1430 | 1406 | } |
| 1431 | 1407 | return null; |
| ... | ... | @@ -1442,6 +1418,24 @@ pub const Tree = struct { |
| 1442 | 1418 | id: AnyId, |
| 1443 | 1419 | name: [:0]const u8, |
| 1444 | 1420 | |
| 1421 | fn search(a: struct { []const u8, Type }, b: Object) std.math.Order { |
| 1422 | if (a[0].ptr != b.name.ptr) { |
| 1423 | const n = @min(a[0].len, b.name.len); |
| 1424 | for (a[0][0..n], b.name[0..n]) |lhs_elem, rhs_elem| { |
| 1425 | switch (std.math.order(lhs_elem, rhs_elem)) { |
| 1426 | .eq => continue, |
| 1427 | .lt => return .lt, |
| 1428 | .gt => return .gt, |
| 1429 | } |
| 1430 | } |
| 1431 | } |
| 1432 | return switch (std.math.order(a[0].len, b.name.len)) { |
| 1433 | .lt => if (a[1] == .directory) std.math.order('/', b.name[a[0].len]) else .lt, |
| 1434 | .gt => if (b.mode.type == .directory) std.math.order(a[0][b.name.len], '/') else .gt, |
| 1435 | .eq => .eq, |
| 1436 | }; |
| 1437 | } |
| 1438 | |
| 1445 | 1439 | pub const Mode = struct { |
| 1446 | 1440 | type: Type, |
| 1447 | 1441 | perm_user: Perm, |
| ... | ... | @@ -1581,8 +1575,8 @@ pub const Tree = struct { |
| 1581 | 1575 | var top = &self.stack.items[self.stack.items.len - 1]; |
| 1582 | 1576 | var containing = top; |
| 1583 | 1577 | var dirname_len = top.dirname_len; |
| 1584 | | if (top.idx < top.tree.children.len()) { |
| 1585 | | const base = top.tree.children.get(top.idx); |
| 1578 | if (top.idx < top.tree.children.len) { |
| 1579 | const base = top.tree.children[top.idx]; |
| 1586 | 1580 | top.idx += 1; |
| 1587 | 1581 | self.name_buffer.shrinkRetainingCapacity(dirname_len); |
| 1588 | 1582 | if (self.name_buffer.items.len != 0) { |
| ... | ... | @@ -1652,106 +1646,3 @@ pub const Ref = struct { |
| 1652 | 1646 | oid: Id, |
| 1653 | 1647 | label: string, |
| 1654 | 1648 | }; |
| 1655 | | |
| 1656 | | /// Variant on std.MultiArrayList but using an allocation per-field rather than a single for the entire structure. |
| 1657 | | fn MultiArrayList(T: type) type { |
| 1658 | | return struct { |
| 1659 | | items: Items, |
| 1660 | | capacity: usize, |
| 1661 | | |
| 1662 | | comptime Elem: type = T, |
| 1663 | | |
| 1664 | | pub const empty: Self = .{ |
| 1665 | | .items = .{}, |
| 1666 | | .capacity = 0, |
| 1667 | | }; |
| 1668 | | |
| 1669 | | pub const Self = @This(); |
| 1670 | | |
| 1671 | | const info = @typeInfo(T).@"struct"; |
| 1672 | | |
| 1673 | | pub const Items = blk: { |
| 1674 | | var names: [info.fields.len][]const u8 = undefined; |
| 1675 | | var types: [info.fields.len]type = undefined; |
| 1676 | | var attrs: [info.fields.len]std.builtin.Type.StructField.Attributes = undefined; |
| 1677 | | for (info.fields, 0..) |f, i| { |
| 1678 | | const empty_slice: []f.type = &[_]f.type{}; |
| 1679 | | names[i] = f.name; |
| 1680 | | types[i] = []f.type; |
| 1681 | | attrs[i] = .{ .default_value_ptr = @ptrCast(&empty_slice) }; |
| 1682 | | } |
| 1683 | | break :blk @Struct(.auto, null, &names, &types, &attrs); |
| 1684 | | }; |
| 1685 | | |
| 1686 | | pub fn deinit(self: *const Self, gpa: std.mem.Allocator) void { |
| 1687 | | inline for (info.fields) |f| { |
| 1688 | | gpa.free(@field(self.items, f.name).ptr[0..self.capacity]); |
| 1689 | | } |
| 1690 | | } |
| 1691 | | |
| 1692 | | pub fn len(self: *const Self) usize { |
| 1693 | | return @field(self.items, info.fields[0].name).len; |
| 1694 | | } |
| 1695 | | |
| 1696 | | pub fn get(self: *const Self, index: usize) T { |
| 1697 | | var result: T = undefined; |
| 1698 | | inline for (info.fields) |f| { |
| 1699 | | @field(result, f.name) = @field(self.items, f.name)[index]; |
| 1700 | | } |
| 1701 | | return result; |
| 1702 | | } |
| 1703 | | |
| 1704 | | pub fn ensureUnusedCapacity(self: *Self, gpa: std.mem.Allocator, additional_count: usize) !void { |
| 1705 | | return self.ensureTotalCapacity(gpa, self.len() + additional_count); |
| 1706 | | } |
| 1707 | | |
| 1708 | | pub fn ensureTotalCapacity(self: *Self, gpa: std.mem.Allocator, new_capacity: usize) !void { |
| 1709 | | if (self.capacity >= new_capacity) return; |
| 1710 | | const actual_new_capacity = growCapacity(self.capacity, new_capacity); |
| 1711 | | const previous_len = self.len(); |
| 1712 | | var new_items: Items = .{}; |
| 1713 | | inline for (info.fields, 0..) |f, i| { |
| 1714 | | errdefer inline for (info.fields[0..i]) |g| gpa.free(@field(new_items, g.name)[0..actual_new_capacity]); |
| 1715 | | @field(new_items, f.name) = try gpa.alloc(f.type, actual_new_capacity); |
| 1716 | | @field(new_items, f.name).len = previous_len; |
| 1717 | | } |
| 1718 | | inline for (info.fields) |f| { |
| 1719 | | @memcpy(@field(new_items, f.name)[0..previous_len], @field(self.items, f.name)[0..previous_len]); |
| 1720 | | } |
| 1721 | | inline for (info.fields) |f| { |
| 1722 | | gpa.free(@field(self.items, f.name)[0..previous_len]); |
| 1723 | | } |
| 1724 | | self.items = new_items; |
| 1725 | | self.capacity = actual_new_capacity; |
| 1726 | | } |
| 1727 | | |
| 1728 | | /// Called when memory growth is necessary. Returns a capacity larger than minimum that grows super-linearly. |
| 1729 | | fn growCapacity(current: usize, minimum: usize) usize { |
| 1730 | | var new = current; |
| 1731 | | while (true) { |
| 1732 | | new +|= new / 2 + init_capacity; |
| 1733 | | if (new >= minimum) return new; |
| 1734 | | } |
| 1735 | | } |
| 1736 | | |
| 1737 | | const init_capacity = init: { |
| 1738 | | var max = 1; |
| 1739 | | for (info.fields) |field| max = @as(comptime_int, @max(max, @sizeOf(field.type))); |
| 1740 | | break :init @as(comptime_int, @max(1, std.atomic.cache_line / max)); |
| 1741 | | }; |
| 1742 | | |
| 1743 | | pub fn append(self: *Self, gpa: std.mem.Allocator, elem: T) !void { |
| 1744 | | try self.ensureUnusedCapacity(gpa, 1); |
| 1745 | | self.appendAssumeCapacity(elem); |
| 1746 | | } |
| 1747 | | |
| 1748 | | pub fn appendAssumeCapacity(self: *Self, elem: T) void { |
| 1749 | | const plen = self.len(); |
| 1750 | | std.debug.assert(plen < self.capacity); |
| 1751 | | inline for (info.fields) |f| { |
| 1752 | | @field(self.items, f.name).len += 1; |
| 1753 | | @field(self.items, f.name)[plen] = @field(elem, f.name); |
| 1754 | | } |
| 1755 | | } |
| 1756 | | }; |
| 1757 | | } |