╭ changed ──────────╮ │src/event/Issue.zig│ ╰───────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │22 }; │ │23 │ │24 pub const tag_max_len = 64; │ │25 + │ │26 +// the moment keys `evt.merge` reads and writes for this kind │ │27 +pub const record_map_key = "event-id->issue"; │ │28 +pub const id_set_key = "issue-id-set"; │ │29 +pub const conflicts_key = "conflicted-issue-id->conflict"; │ │30 +pub const id_to_field_to_oid_key = "issue-id->field->oid"; │ │31 │ │32 // a "tag status" key names a tag's per-status issue set (tags can't contain│ │33 // spaces, so the pair is unambiguous) │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │72 event_maybe: ?@This(), │ │73 arena: *std.heap.ArenaAllocator, │ │74 created_ts: u64, │ │75 + // the commit this event came from, recorded against every field it │ │76 + // changes. null from `merge`, which sets oids and conflicts itself.│ │77 + event_oid: ?[]const u8, │ │78 ) !void { │ │79 const issue_key = hash.hashInt(hash_kind, event_id); │ │80 │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 90 const tag_to_issues_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "tag+status->issue-id-set")); │ │ 91 const tag_to_issues = try DB.SortedMap(.read_write).init(tag_to_issues_cursor); │ │ 92 │ │ 93 + const conflicts_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, conflicts_key)); │ │ 94 + const conflicts = try DB.SortedMap(.read_write).init(conflicts_cursor); │ │ 95 + │ │ 96 + const issue_id_to_field_to_oid_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, id_to_field_to_oid_key));│ │ 97 + const issue_id_to_field_to_oid = try DB.HashMap(.read_write).init(issue_id_to_field_to_oid_cursor); │ │ 98 + │ │ 99 if (event_maybe) |event| { │ │100 var event_to_write = event; │ │101 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │102 + var existing_event_maybe: ?@This() = null; │ │103 const existing_cursor_maybe = try event_id_to_issue.getCursor(issue_key);│ │104 if (existing_cursor_maybe) |existing_cursor| { │ │105 // updates preserve the original creation timestamp │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │106 const existing_issue = try DB.HashMap(.read_only).init(existing_cursor); │ │107 const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_issue);│ │108 + existing_event_maybe = existing_event; │ │109 event_to_write.created_ts = existing_event.created_ts; │ │110 │ │111 // drop the old status's and tags' entries; the current ones are re-added below │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │113 const status_set = try statusSet(DB, status_to_issues, existing_event.status); │ │114 _ = try status_set.remove(&order_key); │ │115 try removeFromTagSets(DB, tag_to_issues, existing_event.tags, existing_event.status, &order_key);│ │116 + │ │117 + // any event settles the conflict, since resolving in the ui may │ │118 + // keep our own values and so change nothing to detect │ │119 + if (event_oid != null) _ = try conflicts.remove(&order_key); │ │120 } else { │ │121 // first time we've seen this issue: stamp it with the commit timestamp │ │122 event_to_write.created_ts = created_ts; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │123 } │ │124 │ │125 + try evt.writeOid(@This(), DB, hash_kind, issue_id_to_field_to_oid, issue_key, existing_event_maybe, event, event_oid);│ │126 + │ │127 const issue_cursor = try event_id_to_issue.putCursor(issue_key); │ │128 const issue = try DB.HashMap(.read_write).init(issue_cursor); │ │129 try evt.upsert(@This(), DB, hash_kind, issue, event_to_write); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │130 │ │131 const order_key = evt.orderKeyDesc(event_to_write.created_ts, event_id); │ │132 │ │133 + // first time we've seen this issue: add it to the set that enumerates │ │134 + // every issue regardless of status │ │135 + if (existing_cursor_maybe == null) { │ │136 + const issue_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, id_set_key));│ │137 + const issue_id_set = try DB.SortedSet(.read_write).init(issue_id_set_cursor); │ │138 + try issue_id_set.put(&order_key); │ │139 + } │ │140 + │ │141 const status_set = try statusSet(DB, status_to_issues, event.status); │ │142 try status_set.put(&order_key); │ │143 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │161 _ = try status_set.remove(&order_key); │ │162 │ │163 try removeFromTagSets(DB, tag_to_issues, existing_event.tags, existing_event.status, &order_key);│ │164 + │ │165 + const issue_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, id_set_key)); │ │166 + const issue_id_set = try DB.SortedSet(.read_write).init(issue_id_set_cursor); │ │167 + _ = try issue_id_set.remove(&order_key); │ │168 + │ │169 + _ = try conflicts.remove(&order_key); │ │170 + _ = try issue_id_to_field_to_oid.remove(issue_key); │ │171 } │ │172 if (!try event_id_to_issue.remove(issue_key)) return error.EventNotFound; │ │173 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯