╭ PatchRev.zig ─────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const evt = @import("../event.zig"); 3 const xit = @import("xit"); 4 const hash = xit.hash; 5 const obj = xit.object; 6 const rp = xit.repo; 7 8 base_oid: []const u8, 9 source_oid: []const u8, 10 target_ref: []const u8, 11 message: []const u8, 12 13 pub const Record = struct { 14 event: Self, 15 removed: bool = false, 16 author_email: ?[]const u8 = null, 17 created_order: u64 = 0, 18 event_oid: []const u8, 19 base_tree_oid: []const u8, 20 head_tree_oid: []const u8, 21 patch_oid: []const u8, 22 }; 23 24 const Self = @This(); 25 26 pub const merge_policy: evt.MergePolicy = .target_wins; 27 pub const record_map_key = "event-id->patchrev"; 28 pub const id_set_key = "patchrev-id-set"; 29 30 pub fn readTrees( 31 comptime repo_kind: rp.RepoKind, 32 comptime repo_opts: rp.RepoOpts(repo_kind), 33 state: rp.Repo(repo_kind, repo_opts).State(.read_only), 34 io: std.Io, 35 allocator: std.mem.Allocator, 36 tree_oid: *const [hash.hexLen(repo_opts.hash)]u8, 37 ) !struct { 38 base: [hash.hexLen(repo_opts.hash)]u8, 39 head: [hash.hexLen(repo_opts.hash)]u8, 40 } { 41 var tree_object = try obj.Object(repo_kind, repo_opts).init(state, io, allocator, tree_oid); 42 defer tree_object.deinit(); 43 44 const entries = switch (tree_object.content) { 45 .tree => |tree| tree.entries, 46 else => return error.InvalidPatch, 47 }; 48 if (entries.count() != 2) return error.InvalidPatch; 49 const base = entries.get("base") orelse return error.InvalidPatch; 50 const head = entries.get("head") orelse return error.InvalidPatch; 51 if (!base.isTree() or !head.isTree()) return error.InvalidPatch; 52 53 const base_oid = std.fmt.bytesToHex(base.oid, .lower); 54 const head_oid = std.fmt.bytesToHex(head.oid, .lower); 55 var base_reader = try obj.ObjectReader(repo_kind, repo_opts).init(state, io, allocator, &base_oid); 56 defer base_reader.deinit(); 57 var head_reader = try obj.ObjectReader(repo_kind, repo_opts).init(state, io, allocator, &head_oid); 58 defer head_reader.deinit(); 59 if (base_reader.header().kind != .tree or head_reader.header().kind != .tree) return error.InvalidPatch; 60 61 return .{ .base = base_oid, .head = head_oid }; 62 } 63 64 pub fn consume( 65 comptime DB: type, 66 comptime hash_kind: hash.HashKind, 67 haxy_moment: DB.HashMap(.read_write), 68 event_id: *const [evt.event_id_size]u8, 69 record_maybe: ?Record, 70 arena: *std.heap.ArenaAllocator, 71 _: ?[]const u8, 72 ) !void { 73 const record_key = hash.hashInt(hash_kind, event_id); 74 const records = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key))); 75 76 var existing_maybe: ?Record = null; 77 const existing_cursor_maybe = try records.getCursor(record_key); 78 if (existing_cursor_maybe) |cursor| { 79 existing_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(cursor)); 80 } 81 82 var record = if (record_maybe) |value| 83 value 84 else blk: { 85 var value = existing_maybe orelse return error.EventNotFound; 86 value.removed = true; 87 break :blk value; 88 }; 89 90 try validateOid(hash_kind, record.event.base_oid); 91 try validateOid(hash_kind, record.event.source_oid); 92 if (!std.mem.startsWith(u8, record.event.target_ref, "refs/heads/") or 93 !xit.ref.validateName(record.event.target_ref["refs/".len..])) return error.InvalidTarget; 94 try validateOid(hash_kind, record.event_oid); 95 try validateOid(hash_kind, record.base_tree_oid); 96 try validateOid(hash_kind, record.head_tree_oid); 97 try validateOid(hash_kind, record.patch_oid); 98 99 if (existing_maybe) |existing| { 100 if (!std.mem.eql(u8, existing.event.base_oid, record.event.base_oid) or 101 !std.mem.eql(u8, existing.event.source_oid, record.event.source_oid) or 102 !std.mem.eql(u8, existing.event.target_ref, record.event.target_ref) or 103 !std.mem.eql(u8, existing.event.message, record.event.message) or 104 !std.mem.eql(u8, existing.base_tree_oid, record.base_tree_oid) or 105 !std.mem.eql(u8, existing.head_tree_oid, record.head_tree_oid) or 106 !std.mem.eql(u8, existing.patch_oid, record.patch_oid)) 107 { 108 return error.PatchRevChanged; 109 } 110 record.created_order = existing.created_order; 111 record.author_email = existing.author_email; 112 record.event_oid = existing.event_oid; 113 } 114 115 const cursor = try records.putCursor(record_key); 116 try evt.upsert(Record, DB, hash_kind, try DB.HashMap(.read_write).init(cursor), record); 117 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .patchrev, record.created_order, record.removed); 118 119 if (existing_cursor_maybe == null) { 120 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, id_set_key))); 121 try ids.put(&evt.orderKeyDesc(record.created_order, event_id)); 122 } 123 } 124 125 fn validateOid(comptime hash_kind: hash.HashKind, oid: []const u8) !void { 126 var bytes: [hash.byteLen(hash_kind)]u8 = undefined; 127 _ = try std.fmt.hexToBytes(&bytes, oid); 128 if (!std.mem.eql(u8, oid, &std.fmt.bytesToHex(bytes, .lower))) return error.InvalidOid; 129 } 130 131 pub fn readById( 132 comptime DB: type, 133 comptime hash_kind: hash.HashKind, 134 haxy_moment: DB.HashMap(.read_only), 135 arena: *std.heap.ArenaAllocator, 136 id: *const [evt.event_id_size]u8, 137 ) !?Record { 138 const records_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, record_map_key)) orelse return null; 139 const records = try DB.HashMap(.read_only).init(records_cursor); 140 const record_cursor = try records.getCursor(hash.hashInt(hash_kind, id)) orelse return null; 141 return try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(record_cursor)); 142 } 143 144 pub const WithId = struct { 145 id: [evt.event_id_size]u8, 146 record: Record, 147 }; 148 149 pub fn readNewest( 150 comptime DB: type, 151 comptime hash_kind: hash.HashKind, 152 haxy_moment: DB.HashMap(.read_only), 153 arena: *std.heap.ArenaAllocator, 154 ) !?WithId { 155 const ids_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, id_set_key)) orelse return null; 156 const ids = try DB.SortedSet(.read_only).init(ids_cursor); 157 var iter = try ids.iteratorFromIndex(0); 158 while (try iter.next()) |cursor| { 159 const id = try evt.readOrderKeyId(DB, cursor); 160 const record = (try readById(DB, hash_kind, haxy_moment, arena, &id)) orelse continue; 161 if (!record.removed) return .{ .id = id, .record = record }; 162 } 163 return null; 164 } 165 166 // derived commits referenced only by patch revision records 167 pub fn gcRoots( 168 comptime DB: type, 169 comptime hash_kind: hash.HashKind, 170 allocator: std.mem.Allocator, 171 haxy_moment: DB.HashMap(.read_only), 172 ) ![][hash.hexLen(hash_kind)]u8 { 173 var roots: std.ArrayList([hash.hexLen(hash_kind)]u8) = .empty; 174 errdefer roots.deinit(allocator); 175 var arena = std.heap.ArenaAllocator.init(allocator); 176 defer arena.deinit(); 177 178 const ids_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, id_set_key)) orelse return try roots.toOwnedSlice(allocator); 179 const ids = try DB.SortedSet(.read_only).init(ids_cursor); 180 var iter = try ids.iteratorFromIndex(0); 181 while (try iter.next()) |cursor| { 182 _ = arena.reset(.retain_capacity); 183 const id = try evt.readOrderKeyId(DB, cursor); 184 const record = (try readById(DB, hash_kind, haxy_moment, &arena, &id)) orelse continue; 185 var oid: [hash.hexLen(hash_kind)]u8 = undefined; 186 if (record.patch_oid.len != oid.len) return error.InvalidOid; 187 @memcpy(&oid, record.patch_oid); 188 try roots.append(allocator, oid); 189 } 190 191 return try roots.toOwnedSlice(allocator); 192 } 193