authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-06-28 01:31:07 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-06-28 01:31:07 -07:00
log1313631aae874c8fa83624cb9538118de62584e7
tree5b6e669b2301a92de9f9c4ab2f51ba3d012966c5
parent00f78a59fc822ae595132f3a01dee19a30d8d23d
signaturebadge-check Signed by SSH key SHA256:4hHJbtBRU58AYXwjL7fkz2fnQHdiye8x1QpTCQ0sHNw

turn unpacked_objects into a sliding window of cached objects


1 files changed, 81 insertions(+), 44 deletions(-)

git.zig+81-44
...@@ -721,11 +721,13 @@ pub const Repository = struct {...@@ -721,11 +721,13 @@ pub const Repository = struct {
721 gpa: std.mem.Allocator,721 gpa: std.mem.Allocator,
722 unpacked_loose_objects: std.StringArrayHashMapUnmanaged(GitObject),722 unpacked_loose_objects: std.StringArrayHashMapUnmanaged(GitObject),
723 unpacked_objects: std.AutoArrayHashMapUnmanaged(u64, GitObject),723 unpacked_objects: std.AutoArrayHashMapUnmanaged(u64, GitObject),
724 unpacked_objects_window: usize,
724 idx_content: std.StringArrayHashMapUnmanaged([]const u8),725 idx_content: std.StringArrayHashMapUnmanaged([]const u8),
725 pack_content: std.StringArrayHashMapUnmanaged([]const u8),726 pack_content: std.StringArrayHashMapUnmanaged([]const u8),
726 commits: std.StringArrayHashMapUnmanaged(*Commit),727 commits: std.StringArrayHashMapUnmanaged(*Commit),
727 trees: std.StringArrayHashMapUnmanaged(*Tree),728 trees: std.StringArrayHashMapUnmanaged(*Tree),
728 tags: std.StringArrayHashMapUnmanaged(*Tag),729 tags: std.StringArrayHashMapUnmanaged(*Tag),
730 mailmap_content: []const u8,
729 mailmap: std.hash_map.StringHashMapUnmanaged([]const u8),731 mailmap: std.hash_map.StringHashMapUnmanaged([]const u8),
730 mailmap_names: std.hash_map.StringHashMapUnmanaged([]const u8),732 mailmap_names: std.hash_map.StringHashMapUnmanaged([]const u8),
731733
...@@ -737,11 +739,13 @@ pub const Repository = struct {...@@ -737,11 +739,13 @@ pub const Repository = struct {
737 .gpa = gpa,739 .gpa = gpa,
738 .unpacked_loose_objects = .empty,740 .unpacked_loose_objects = .empty,
739 .unpacked_objects = .empty,741 .unpacked_objects = .empty,
742 .unpacked_objects_window = 0,
740 .idx_content = .empty,743 .idx_content = .empty,
741 .pack_content = .empty,744 .pack_content = .empty,
742 .commits = .empty,745 .commits = .empty,
743 .trees = .empty,746 .trees = .empty,
744 .tags = .empty,747 .tags = .empty,
748 .mailmap_content = "",
745 .mailmap = .empty,749 .mailmap = .empty,
746 .mailmap_names = .empty,750 .mailmap_names = .empty,
747 };751 };
...@@ -795,6 +799,7 @@ pub const Repository = struct {...@@ -795,6 +799,7 @@ pub const Repository = struct {
795 }799 }
796 }800 }
797 }801 }
802 r.mailmap_content = mailmap_content;
798 r.mailmap = mailmap;803 r.mailmap = mailmap;
799 r.mailmap_names = mailmap_names;804 r.mailmap_names = mailmap_names;
800 }805 }
...@@ -966,7 +971,19 @@ pub const Repository = struct {...@@ -966,7 +971,19 @@ pub const Repository = struct {
966 const _type = std.meta.stringToEnum(RefType, @tagName(ty)).?;971 const _type = std.meta.stringToEnum(RefType, @tagName(ty)).?;
967 const content = try list.toOwnedSlice();972 const content = try list.toOwnedSlice();
968 const obj: GitObject = .{ .type = _type, .content = content };973 const obj: GitObject = .{ .type = _type, .content = content };
969 if (cache_behavior == .cache) try r.unpacked_objects.put(r.gpa, key, obj);974 const window_max = 1024 * 128;
975 if (cache_behavior == .cache) {
976 if (r.unpacked_objects.count() < window_max) {
977 try r.unpacked_objects.put(r.gpa, key, obj);
978 } else {
979 const item = &r.unpacked_objects.values()[r.unpacked_objects_window];
980 r.gpa.free(item.content);
981 r.unpacked_objects.keys()[r.unpacked_objects_window] = key;
982 item.* = obj;
983 }
984 r.unpacked_objects_window += 1;
985 r.unpacked_objects_window %= window_max;
986 }
970 return obj;987 return obj;
971 },988 },
972 .ofs_delta => {989 .ofs_delta => {
...@@ -1064,7 +1081,19 @@ pub const Repository = struct {...@@ -1064,7 +1081,19 @@ pub const Repository = struct {
1064 const _type = base_obj.type;1081 const _type = base_obj.type;
1065 const content = try list2.toOwnedSlice(r.gpa);1082 const content = try list2.toOwnedSlice(r.gpa);
1066 const obj: GitObject = .{ .type = _type, .content = content };1083 const obj: GitObject = .{ .type = _type, .content = content };
1067 if (cache_behavior == .cache) try r.unpacked_objects.put(r.gpa, key, obj);1084 const window_max = 1024 * 128;
1085 if (cache_behavior == .cache) {
1086 if (r.unpacked_objects.count() < window_max) {
1087 try r.unpacked_objects.put(r.gpa, key, obj);
1088 } else {
1089 const item = &r.unpacked_objects.values()[r.unpacked_objects_window];
1090 r.gpa.free(item.content);
1091 r.unpacked_objects.keys()[r.unpacked_objects_window] = key;
1092 item.* = obj;
1093 }
1094 r.unpacked_objects_window += 1;
1095 r.unpacked_objects_window %= window_max;
1096 }
1068 return obj;1097 return obj;
1069 }1098 }
10701099
...@@ -1090,9 +1119,9 @@ pub const Repository = struct {...@@ -1090,9 +1119,9 @@ pub const Repository = struct {
1090 pub fn getBlob(r: *Repository, id: BlobId, cache_behavior: CacheBehavior) !?[]const u8 {1119 pub fn getBlob(r: *Repository, id: BlobId, cache_behavior: CacheBehavior) !?[]const u8 {
1091 if (try r.getObject(id.id, cache_behavior)) |obj| {1120 if (try r.getObject(id.id, cache_behavior)) |obj| {
1092 if (obj.type == .blob) {1121 if (obj.type == .blob) {
1122 if (cache_behavior == .cache) return try r.gpa.dupe(u8, obj.content);
1093 return obj.content;1123 return obj.content;
1094 }1124 }
1095 if (cache_behavior == .no_cache) r.gpa.free(obj.content);
1096 }1125 }
1097 return null;1126 return null;
1098 }1127 }
...@@ -1108,18 +1137,16 @@ pub const Repository = struct {...@@ -1108,18 +1137,16 @@ pub const Repository = struct {
1108 if (cache_behavior == .cache) if (r.commits.get(id.id)) |val| {1137 if (cache_behavior == .cache) if (r.commits.get(id.id)) |val| {
1109 return .{ id, val };1138 return .{ id, val };
1110 };1139 };
1111 if (try r.getObject(id.id, cache_behavior)) |obj| {1140 if (try r.getObject(id.id, .cache)) |obj| {
1112 if (obj.type == .commit) {1141 if (obj.type == .commit) {
1113 errdefer if (cache_behavior == .no_cache) r.gpa.free(obj.content);
1114 const raw = try r.gpa.dupe(u8, obj.content);1142 const raw = try r.gpa.dupe(u8, obj.content);
1115 errdefer r.gpa.free(raw);1143 errdefer r.gpa.free(raw);
1116 const commit = try r.gpa.create(Commit);1144 const commit = try r.gpa.create(Commit);
1117 errdefer r.gpa.destroy(commit);1145 errdefer r.gpa.destroy(commit);
1118 commit.* = try parseCommit(r.gpa, obj.content, &r.mailmap, &r.mailmap_names);1146 commit.* = try parseCommit(r.gpa, raw, &r.mailmap, &r.mailmap_names);
1119 try r.commits.put(r.gpa, id.id, commit);1147 if (cache_behavior == .cache) try r.commits.put(r.gpa, id.id, commit);
1120 return .{ id, commit };1148 return .{ id, commit };
1121 }1149 }
1122 if (cache_behavior == .no_cache) r.gpa.free(obj.content);
1123 }1150 }
1124 return null;1151 return null;
1125 }1152 }
...@@ -1135,25 +1162,24 @@ pub const Repository = struct {...@@ -1135,25 +1162,24 @@ pub const Repository = struct {
1135 if (cache_behavior == .cache) if (r.trees.get(id.id)) |val| {1162 if (cache_behavior == .cache) if (r.trees.get(id.id)) |val| {
1136 return .{ id, val };1163 return .{ id, val };
1137 };1164 };
1138 if (try r.getObject(id.id, cache_behavior)) |obj| {1165 if (try r.getObject(id.id, .cache)) |obj| {
1139 if (obj.type == .tree) {1166 if (obj.type == .tree) {
1140 errdefer if (cache_behavior == .no_cache) r.gpa.free(obj.content);
1141 const raw = try r.gpa.dupe(u8, obj.content);1167 const raw = try r.gpa.dupe(u8, obj.content);
1142 errdefer r.gpa.free(raw);1168 errdefer r.gpa.free(raw);
1143 var children: std.ArrayList(Tree.Object) = .empty;1169 var children: std.ArrayList(Tree.Object) = .empty;
1144 errdefer children.deinit(r.gpa);1170 errdefer children.deinit(r.gpa);
1145 try children.ensureUnusedCapacity(r.gpa, 33);1171 try children.ensureUnusedCapacity(r.gpa, 33);
1146 var i: usize = 0;1172 var i: usize = 0;
1147 while (i < obj.content.len) {1173 while (i < raw.len) {
1148 const mode_end = std.mem.indexOfScalar(u8, obj.content[i..], ' ').?;1174 const mode_end = std.mem.indexOfScalar(u8, raw[i..], ' ').?;
1149 const mode = obj.content[i..][0..mode_end];1175 const mode = raw[i..][0..mode_end];
1150 i += mode_end + 1;1176 i += mode_end + 1;
11511177
1152 const name_end = std.mem.indexOfScalar(u8, obj.content[i..], 0).?;1178 const name_end = std.mem.indexOfScalar(u8, raw[i..], 0).?;
1153 const name = obj.content[i..][0..name_end :0];1179 const name = raw[i..][0..name_end :0];
1154 i += name_end + 1;1180 i += name_end + 1;
11551181
1156 const oid_raw = obj.content[i..][0..20].*;1182 const oid_raw = raw[i..][0..20].*;
1157 const oid_hex = extras.to_hex(oid_raw);1183 const oid_hex = extras.to_hex(oid_raw);
1158 i += 20;1184 i += 20;
11591185
...@@ -1180,11 +1206,10 @@ pub const Repository = struct {...@@ -1180,11 +1206,10 @@ pub const Repository = struct {
1180 };1206 };
1181 const tree = try r.gpa.create(Tree);1207 const tree = try r.gpa.create(Tree);
1182 errdefer r.gpa.destroy(tree);1208 errdefer r.gpa.destroy(tree);
1183 tree.* = .{ .raw = obj.content, .children = children_slice };1209 tree.* = .{ .raw = raw, .children = children_slice };
1184 if (cache_behavior == .cache) try r.trees.put(r.gpa, id.id, tree);1210 if (cache_behavior == .cache) try r.trees.put(r.gpa, id.id, tree);
1185 return .{ id, tree };1211 return .{ id, tree };
1186 }1212 }
1187 if (cache_behavior == .no_cache) r.gpa.free(obj.content);
1188 }1213 }
1189 return null;1214 return null;
1190 }1215 }
...@@ -1200,18 +1225,16 @@ pub const Repository = struct {...@@ -1200,18 +1225,16 @@ pub const Repository = struct {
1200 if (cache_behavior == .cache) if (r.tags.get(id.id)) |val| {1225 if (cache_behavior == .cache) if (r.tags.get(id.id)) |val| {
1201 return .{ id, val };1226 return .{ id, val };
1202 };1227 };
1203 if (try r.getObject(id.id, cache_behavior)) |obj| {1228 if (try r.getObject(id.id, .cache)) |obj| {
1204 if (obj.type == .tag) {1229 if (obj.type == .tag) {
1205 errdefer if (cache_behavior == .no_cache) r.gpa.free(obj.content);
1206 const raw = try r.gpa.dupe(u8, obj.content);1230 const raw = try r.gpa.dupe(u8, obj.content);
1207 errdefer r.gpa.free(raw);1231 errdefer r.gpa.free(raw);
1208 const tag = try r.gpa.create(Tag);1232 const tag = try r.gpa.create(Tag);
1209 errdefer r.gpa.destroy(tag);1233 errdefer r.gpa.destroy(tag);
1210 tag.* = try parseTag(obj.content);1234 tag.* = try parseTag(raw);
1211 if (cache_behavior == .cache) try r.tags.put(r.gpa, id.id, tag);1235 if (cache_behavior == .cache) try r.tags.put(r.gpa, id.id, tag);
1212 return .{ id, tag };1236 return .{ id, tag };
1213 }1237 }
1214 if (cache_behavior == .no_cache) r.gpa.free(obj.content);
1215 }1238 }
1216 return null;1239 return null;
1217 }1240 }
...@@ -1301,9 +1324,10 @@ pub const Repository = struct {...@@ -1301,9 +1324,10 @@ pub const Repository = struct {
13011324
1302 // const start = time.milliTimestamp();1325 // const start = time.milliTimestamp();
13031326
1304 const base = try r.getCommitA(base_oid.id, .cache);1327 const base = try r.getCommitA(base_oid.id, .no_cache);
1305 const base_tree_id = (try traverseTo(r, base.tree, dir_path)).?;1328 const base_tree_id, const base_tree_id_parent = try traverseTo(r, base.tree, dir_path);
1306 const base_tree = try r.getTreeA(base_tree_id.id, .cache);1329 defer if (base_tree_id_parent) |p| p.destroy(r);
1330 const base_tree = try r.getTreeA(base_tree_id.?.id, .cache);
1307 const total = base_tree.children.len;1331 const total = base_tree.children.len;
13081332
1309 var found: usize = 0;1333 var found: usize = 0;
...@@ -1317,29 +1341,38 @@ pub const Repository = struct {...@@ -1317,29 +1341,38 @@ pub const Repository = struct {
1317 var searched: usize = 1;1341 var searched: usize = 1;
1318 var commit_id_prev = base_oid;1342 var commit_id_prev = base_oid;
1319 var commit_id = base_oid;1343 var commit_id = base_oid;
1344 var commit_prev_prev: ?*Commit = null;
1345 var commit_prev: ?*Commit = null;
1320 var commit = base;1346 var commit = base;
1321 var tree_id = base_tree_id;1347 var tree_id = base_tree_id.?;
1322 while (true) {1348 while (true) : ({
1323 if (commit.parents.len == 0) break;1349 if (commit_prev_prev) |p| p.destroy(r);
1324 commit_id, commit = (try r.getCommit(commit.parents[0], .cache)).?;1350 commit_prev_prev = commit_prev;
1351 commit_prev = commit;
1352 commit_id_prev = commit_id;
1353 commit_id, commit = (try r.getCommit(commit.parents[0], .no_cache)).?;
1354 }) {
1325 searched += 1;1355 searched += 1;
1326 defer commit_id_prev = commit_id;1356 if (commit.parents.len == 0) break;
1327 const new_tree_id = try traverseTo(r, commit.tree, dir_path) orelse {1357 const new_tree_id, const new_tree_id_parent = try traverseTo(r, commit.tree, dir_path);
1358 defer if (new_tree_id_parent) |p| p.destroy(r);
1359 if (new_tree_id == null) {
1328 var i: usize = 0;1360 var i: usize = 0;
1329 while (findFirstUnset(set, i)) |j| : (i += 1) {1361 while (findFirstUnset(set, i)) |j| : (i += 1) {
1330 i = j;1362 i = j;
1331 const k = result.keys()[i];1363 const k = result.keys()[i];
1332 found += 1;1364 found += 1;
1333 result.putAssumeCapacity(k, commit_id_prev);1365 result.putAssumeCapacity(k, .{ .id = (try r.gpa.dupe(u8, commit_id_prev.id))[0..40] });
1334 set.set(i);1366 set.set(i);
1335 // std.log.debug("found [{d}/{d}] objects after searching {d} commits, found {s}", .{ found, total, searched, k });1367 // std.log.debug("found [{d}/{d}] objects after searching {d} commits, found {s}", .{ found, total, searched, k });
1336 continue;1368 continue;
1337 }1369 }
1338 break;1370 break;
1339 };1371 }
1340 if (new_tree_id.eql(tree_id)) continue;1372 if (new_tree_id.?.eql(tree_id)) continue;
1341 tree_id = new_tree_id;1373 tree_id = new_tree_id.?;
1342 const tree = try r.getTreeA(tree_id.id, .cache);1374 const tree = try r.getTreeA(tree_id.id, .no_cache);
1375 defer tree.destroy(r);
1343 var i: usize = 0;1376 var i: usize = 0;
1344 while (findFirstUnset(set, i)) |j| : (i += 1) {1377 while (findFirstUnset(set, i)) |j| : (i += 1) {
1345 i = j;1378 i = j;
...@@ -1347,14 +1380,14 @@ pub const Repository = struct {...@@ -1347,14 +1380,14 @@ pub const Repository = struct {
1347 const new = tree.get(k);1380 const new = tree.get(k);
1348 if (new == null) {1381 if (new == null) {
1349 found += 1;1382 found += 1;
1350 result.putAssumeCapacity(k, commit_id_prev);1383 result.putAssumeCapacity(k, .{ .id = (try r.gpa.dupe(u8, commit_id_prev.id))[0..40] });
1351 set.set(i);1384 set.set(i);
1352 // std.log.debug("found [{d}/{d}] objects after searching {d} commits, at {d} found {s}", .{ found, total, searched, i, k });1385 // std.log.debug("found [{d}/{d}] objects after searching {d} commits, at {d} found {s}", .{ found, total, searched, i, k });
1353 continue;1386 continue;
1354 }1387 }
1355 if (!std.mem.eql(u8, new.?.id.erase(), base_tree.children[i].id.erase())) {1388 if (!std.mem.eql(u8, new.?.id.erase(), base_tree.children[i].id.erase())) {
1356 found += 1;1389 found += 1;
1357 result.putAssumeCapacity(k, commit_id_prev);1390 result.putAssumeCapacity(k, .{ .id = (try r.gpa.dupe(u8, commit_id_prev.id))[0..40] });
1358 set.set(i);1391 set.set(i);
1359 // std.log.debug("found [{d}/{d}] objects after searching {d} commits, at {d} found {s}", .{ found, total, searched, i, k });1392 // std.log.debug("found [{d}/{d}] objects after searching {d} commits, at {d} found {s}", .{ found, total, searched, i, k });
1360 continue;1393 continue;
...@@ -1709,17 +1742,21 @@ const ZlibCode = enum(c_int) {...@@ -1709,17 +1742,21 @@ const ZlibCode = enum(c_int) {
1709 Z_VERSION_ERROR = -6,1742 Z_VERSION_ERROR = -6,
1710};1743};
17111744
1712fn traverseTo(r: *Repository, treestart_id: TreeId, dir_path: []const u8) !?TreeId {1745fn traverseTo(r: *Repository, treestart_id: TreeId, dir_path: []const u8) !struct { ?TreeId, ?*Tree } {
1713 var id = treestart_id;1746 var id = treestart_id;
1714 if (dir_path.len == 0) return id;1747 if (dir_path.len == 0) return .{ id, null };
1715 var iter = std.mem.splitScalar(u8, dir_path, '/');1748 var iter = std.mem.splitScalar(u8, dir_path, '/');
1749 var prev: ?*Tree = null;
1716 while (iter.next()) |segment| {1750 while (iter.next()) |segment| {
1717 const tree = try r.getTreeA(id.id, .cache);1751 const p = prev;
1718 const o = tree.get(segment) orelse return null;1752 const tree = try r.getTreeA(id.id, .no_cache);
1719 if (o.id != .tree) return null;1753 defer prev = tree;
1754 if (p) |_| p.?.destroy(r);
1755 const o = tree.get(segment) orelse return .{ null, tree };
1756 if (o.id != .tree) return .{ null, tree };
1720 id = o.id.tree;1757 id = o.id.tree;
1721 }1758 }
1722 return id;1759 return .{ id, prev };
1723}1760}
17241761
1725pub const Tree = struct {1762pub const Tree = struct {