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