╭ Patch.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 6 title: []const u8, 7 description: []const u8, 8 tags: []const u8, // space-separated 9 target_ref: []const u8, 10 target_patch_id: ?[evt.event_id_size * 2]u8 = null, 11 patchrev_id: [evt.event_id_size * 2]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 }; 19 20 const Self = @This(); 21 22 pub const tag_max_len = 64; 23 pub const merge_policy: evt.MergePolicy = .field_conflicts; 24 pub const record_map_key = "event-id->patch"; 25 pub const id_set_key = "patch-id-set"; 26 pub const conflicts_key = "conflicted-patch-id->conflict"; 27 pub const id_to_field_to_oid_key = "patch-id->field->oid"; 28 pub const target_patch_id_to_patch_id_set_key = "target-patch-id->patch-id-set"; 29 30 pub fn fieldsValid(title: []const u8, tags: []const u8) bool { 31 if (title.len == 0) return false; 32 var tag_iter = std.mem.splitScalar(u8, tags, ' '); 33 while (tag_iter.next()) |tag| { 34 if (tag.len > tag_max_len) return false; 35 } 36 return true; 37 } 38 39 pub fn consume( 40 comptime DB: type, 41 comptime hash_kind: hash.HashKind, 42 haxy_moment: DB.HashMap(.read_write), 43 event_id: *const [evt.event_id_size]u8, 44 record_maybe: ?Record, 45 arena: *std.heap.ArenaAllocator, 46 event_oid: ?[]const u8, 47 ) !void { 48 const record_key = hash.hashInt(hash_kind, event_id); 49 const records = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key))); 50 const conflicts = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, conflicts_key))); 51 const field_oid_map = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, id_to_field_to_oid_key))); 52 53 var existing_maybe: ?Record = null; 54 const existing_cursor_maybe = try records.getCursor(record_key); 55 if (existing_cursor_maybe) |cursor| { 56 existing_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(cursor)); 57 } 58 59 var record = if (record_maybe) |value| 60 value 61 else blk: { 62 var value = existing_maybe orelse return error.EventNotFound; 63 value.removed = true; 64 break :blk value; 65 }; 66 67 if (!fieldsValid(record.event.title, record.event.tags) or record.event.target_ref.len == 0) { 68 return error.InvalidPatch; 69 } 70 const patchrev_id = try evt.parseEventId(&record.event.patchrev_id); 71 const target_patch_id = if (record.event.target_patch_id) |*id| try evt.parseEventId(id) else null; 72 if (target_patch_id) |*id| { 73 if (std.mem.eql(u8, id, event_id)) return error.InvalidPatch; 74 } 75 76 if (existing_maybe) |existing| { 77 record.created_order = existing.created_order; 78 record.author_email = existing.author_email; 79 if (event_oid != null) { 80 if (!std.meta.eql(existing.event.target_patch_id, record.event.target_patch_id)) { 81 return error.TargetPatchChanged; 82 } 83 } 84 if (event_oid != null or record.removed) { 85 _ = try conflicts.remove(&evt.orderKeyDesc(existing.created_order, event_id)); 86 } 87 } 88 89 if (event_oid != null) { 90 const changed = if (existing_maybe) |existing| !std.mem.eql(u8, &existing.event.patchrev_id, &record.event.patchrev_id) else true; 91 if (changed) { 92 const patchrev = (try evt.PatchRev.readById(DB, hash_kind, haxy_moment.readOnly(), arena, &patchrev_id)) orelse return error.PatchRevNotFound; 93 if (patchrev.removed) return error.PatchRevNotFound; 94 if (target_patch_id) |*target_id| { 95 const target = (try readById(DB, hash_kind, haxy_moment.readOnly(), arena, target_id)) orelse return error.TargetPatchNotFound; 96 if (target.removed) return error.TargetPatchNotFound; 97 const target_patchrev_id = try evt.parseEventId(&target.event.patchrev_id); 98 const target_patchrev = (try evt.PatchRev.readById(DB, hash_kind, haxy_moment.readOnly(), arena, &target_patchrev_id)) orelse return error.PatchRevNotFound; 99 if (target_patchrev.removed) return error.PatchRevNotFound; 100 if (!std.mem.eql(u8, patchrev.event.base_oid, target_patchrev.patch_oid) or 101 !std.mem.eql(u8, patchrev.base_tree_oid, target_patchrev.head_tree_oid)) 102 { 103 return error.InvalidPatchBase; 104 } 105 } 106 } 107 } 108 109 try evt.writeOid(Self, DB, hash_kind, field_oid_map, record_key, if (existing_maybe) |existing| existing.event else null, record.event, event_oid); 110 111 const cursor = try records.putCursor(record_key); 112 try evt.upsert(Record, DB, hash_kind, try DB.HashMap(.read_write).init(cursor), record); 113 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .patch, record.created_order, record.removed); 114 115 if (existing_cursor_maybe == null) { 116 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, id_set_key))); 117 try ids.put(&evt.orderKeyDesc(record.created_order, event_id)); 118 if (target_patch_id) |*target_id| { 119 const targets = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, target_patch_id_to_patch_id_set_key))); 120 const children = try DB.SortedSet(.read_write).init(try targets.putCursor(hash.hashInt(hash_kind, target_id))); 121 try children.put(&evt.orderKeyDesc(record.created_order, event_id)); 122 } 123 } 124 } 125 126 pub fn readById( 127 comptime DB: type, 128 comptime hash_kind: hash.HashKind, 129 haxy_moment: DB.HashMap(.read_only), 130 arena: *std.heap.ArenaAllocator, 131 id: *const [evt.event_id_size]u8, 132 ) !?Record { 133 const records_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, record_map_key)) orelse return null; 134 const records = try DB.HashMap(.read_only).init(records_cursor); 135 const record_cursor = try records.getCursor(hash.hashInt(hash_kind, id)) orelse return null; 136 return try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(record_cursor)); 137 } 138