╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │1117 }; │ │1118 │ │1119 // what a three-way merge did with a field │ │1120 -const FieldMerge = enum { │ │1120 +pub const FieldMerge = enum { │ │1121 // the target's value stands, because neither side changed it, both made the│ │1122 // same change, or only the target changed it │ │1123 kept, │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1249 outcome = @splat(.kept); │ │1250 merged = target_record; │ │1251 merged.event = mergeFields(T, if (baseline_record) |baseline| baseline.event else null, target_record.event, parent_record.event, &outcome);│ │1252 + if (comptime @hasDecl(T, "resolveMerge")) { │ │1253 + T.resolveMerge(target_record.event, parent_record.event, &merged.event, &outcome); │ │1254 + } │ │1255 │ │1256 // merge the removed state separately from serialized fields. removal │ │1257 // carries over an unchanged record, while an edit or restoration │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/Fork.zig│ ╰──────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │ 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 { │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────╮ │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"; │ │22 -pub const repo_id_to_fork_id_set_key = "repo-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;│ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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)));│ │41 - const repo_forks = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, repo_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); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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);│ │66 - try removeFromParentSet(DB, hash_kind, repo_forks, existing.event.repo_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 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 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); │ │ 83 - const by_repo = try DB.SortedSet(.read_write).init(try repo_forks.putCursor(hash.hashInt(hash_kind, record.event.repo_id)));│ │ 84 - try by_repo.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 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/event/Patch.zig│ ╰───────────────────╯ ╭───────────────────────────────────────────────────────╮ │ 8 tags: []const u8, // space-separated │ │ 9 target_patch_id: ?[evt.event_id_size * 2]u8 = null,│ │10 patchrev_id: ?[evt.event_id_size * 2]u8 = null, │ │11 +status: Status = .open, │ │12 │ │13 pub const Record = struct { │ │14 event: Self, │ ╰───────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯