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