╭ Fork.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 user_id: []const u8, 7 repo_id: []const u8, 8 stage: Stage = .draft, 9 10 // what the db stores: the event's data plus the commit-derived fields 11 pub const Record = struct { 12 event: Self, 13 removed: bool = false, 14 created_order: u64 = 0, 15 }; 16 17 const Self = @This(); 18 19 pub const Stage = enum { 20 draft, 21 posted, 22 }; 23 24 pub const merge_policy: evt.MergePolicy = .target_wins; 25 pub const record_map_key = "event-id->fork"; 26 pub const id_set_key = "fork-id-set"; 27 pub const user_id_to_fork_id_set_key = "user-id->fork-id-set"; 28 pub const repo_user_to_draft_id_set_key = "repo+user->draft-id-set"; 29 pub const DraftKey = [evt.event_id_size * 2]u8; 30 31 pub fn draftKey(repo_id: []const u8, user_id: []const u8) DraftKey { 32 var key: DraftKey = undefined; 33 @memcpy(key[0..evt.event_id_size], repo_id); 34 @memcpy(key[evt.event_id_size..], user_id); 35 return key; 36 } 37 38 fn validate(record: Record) !void { 39 if (record.event.user_id.len != evt.event_id_size) return error.InvalidUserId; 40 if (record.event.repo_id.len != evt.event_id_size) return error.InvalidRepoId; 41 } 42 43 pub fn consume( 44 comptime DB: type, 45 comptime hash_kind: hash.HashKind, 46 haxy_moment: DB.HashMap(.read_write), 47 event_id: *const [evt.event_id_size]u8, 48 record_maybe: ?Record, 49 arena: *std.heap.ArenaAllocator, 50 _: ?[]const u8, 51 ) !void { 52 const fork_key = hash.hashInt(hash_kind, event_id); 53 const records = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key))); 54 const user_forks = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, user_id_to_fork_id_set_key))); 55 const drafts = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, repo_user_to_draft_id_set_key))); 56 57 var existing_maybe: ?Record = null; 58 const existing_cursor_maybe = try records.getCursor(fork_key); 59 if (existing_cursor_maybe) |cursor| { 60 existing_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(cursor)); 61 } 62 63 var record = if (record_maybe) |value| 64 value 65 else blk: { 66 var existing = existing_maybe orelse return error.EventNotFound; 67 existing.removed = true; 68 break :blk existing; 69 }; 70 71 if (!record.removed) try validate(record); 72 73 if (existing_maybe) |existing| { 74 if (!std.mem.eql(u8, existing.event.user_id, record.event.user_id) or 75 !std.mem.eql(u8, existing.event.repo_id, record.event.repo_id)) return error.ForkChanged; 76 if (existing.event.stage == .posted and record.event.stage != .posted) return error.ForkAlreadyPosted; 77 record.created_order = existing.created_order; 78 if (!existing.removed) { 79 const order_key = evt.orderKeyDesc(existing.created_order, event_id); 80 try removeFromParentSet(DB, hash_kind, user_forks, existing.event.user_id, &order_key); 81 if (existing.event.stage == .draft) { 82 const key = draftKey(existing.event.repo_id, existing.event.user_id); 83 try removeFromParentSet(DB, hash_kind, drafts, &key, &order_key); 84 } 85 } 86 } 87 88 const fork_cursor = try records.putCursor(fork_key); 89 try evt.upsert(Record, DB, hash_kind, try DB.HashMap(.read_write).init(fork_cursor), record); 90 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .fork, record.created_order, record.removed); 91 92 const order_key = evt.orderKeyDesc(record.created_order, event_id); 93 if (existing_cursor_maybe == null) { 94 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, id_set_key))); 95 try ids.put(&order_key); 96 } 97 98 if (!record.removed) { 99 const by_user = try DB.SortedSet(.read_write).init(try user_forks.putCursor(hash.hashInt(hash_kind, record.event.user_id))); 100 try by_user.put(&order_key); 101 if (record.event.stage == .draft) { 102 const key = draftKey(record.event.repo_id, record.event.user_id); 103 const by_repo_user = try DB.SortedSet(.read_write).init(try drafts.putCursor(hash.hashInt(hash_kind, &key))); 104 try by_repo_user.put(&order_key); 105 } 106 } 107 } 108 109 fn removeFromParentSet( 110 comptime DB: type, 111 comptime hash_kind: hash.HashKind, 112 index: DB.HashMap(.read_write), 113 parent_id: []const u8, 114 order_key: *const [@sizeOf(u64) + evt.event_id_size]u8, 115 ) !void { 116 const cursor = try index.putCursor(hash.hashInt(hash_kind, parent_id)); 117 const ids = try DB.SortedSet(.read_write).init(cursor); 118 _ = try ids.remove(order_key); 119 } 120 121 pub fn readById( 122 comptime DB: type, 123 comptime hash_kind: hash.HashKind, 124 haxy_moment: DB.HashMap(.read_only), 125 arena: *std.heap.ArenaAllocator, 126 fork_id: []const u8, 127 ) !?Record { 128 const records_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, record_map_key)) orelse return null; 129 const records = try DB.HashMap(.read_only).init(records_cursor); 130 const fork_cursor = try records.getCursor(hash.hashInt(hash_kind, fork_id)) orelse return null; 131 return try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(fork_cursor)); 132 } 133