╭ changed ──────────╮ │src/event/Patch.zig│ ╰───────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │ 7 description: []const u8, │ │ 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, │ │10 +revision: ?Revision = null, │ │11 status: Status = .open, │ │12 + │ │13 +pub const Revision = struct { │ │14 + id: [evt.event_id_size * 2]u8, │ │15 + squash_oid: []const u8, │ │16 + source_oid: []const u8, │ │17 + target_ref: []const u8, │ │18 + │ │19 + pub fn fromRecord(id: [evt.event_id_size]u8, record: evt.PatchRev.Record) Revision {│ │20 + return .{ │ │21 + .id = std.fmt.bytesToHex(id, .lower), │ │22 + .squash_oid = record.patch_oid, │ │23 + .source_oid = record.event.source_oid, │ │24 + .target_ref = record.event.target_ref, │ │25 + }; │ │26 + } │ │27 + │ │28 + pub fn matches(self: Revision, record: evt.PatchRev.Record) bool { │ │29 + return !record.removed and │ │30 + std.mem.eql(u8, record.patch_oid, self.squash_oid) and │ │31 + std.mem.eql(u8, record.event.source_oid, self.source_oid) and │ │32 + std.mem.eql(u8, record.event.target_ref, self.target_ref); │ │33 + } │ │34 +}; │ │35 │ │36 pub const Record = struct { │ │37 event: Self, │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │63 pub const target_patch_id_to_patch_id_set_key = "target-patch-id->patch-id-set";│ │64 pub const status_to_id_set_key = "status->patch-id-set"; │ │65 pub const tag_status_to_id_set_key = "tag+status->patch-id-set"; │ │66 +pub const revision_to_id_set_key = "target-ref+oid->patch-id-set"; │ │67 │ │68 pub const TagStatusKey = [tag_max_len + 1 + Status.longest_len]u8; │ │69 │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │115 }; │ │116 │ │117 if (!fieldsValid(record.event.title, record.event.tags)) return error.InvalidPatch; │ │ 94 - const patchrev_id = if (record.event.patchrev_id) |*id| try evt.parseEventId(id) else null; │ │118 + const revision_id = if (record.event.revision) |*revision| blk: { │ │119 + try evt.PatchRev.validateOid(hash_kind, revision.squash_oid); │ │120 + try evt.PatchRev.validateOid(hash_kind, revision.source_oid); │ │121 + try evt.PatchRev.validateTarget(revision.target_ref); │ │122 + break :blk try evt.parseEventId(&revision.id); │ │123 + } else null; │ │124 const target_patch_id = if (record.event.target_patch_id) |*id| try evt.parseEventId(id) else null;│ │125 if (target_patch_id) |*id| { │ │126 if (std.mem.eql(u8, id, event_id)) return error.InvalidPatch; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │138 } │ │139 │ │140 if (record.event.status == .merged) { │ │112 - const revision_id = patchrev_id orelse return error.InvalidPatch; │ │113 - const revision = (try evt.PatchRev.readById(DB, hash_kind, haxy_moment.readOnly(), arena, &revision_id)) orelse return error.InvalidPatch;│ │114 - if (revision.removed) return error.InvalidPatch; │ │141 + const id = revision_id orelse return error.InvalidPatch; │ │142 + const selected = record.event.revision orelse return error.InvalidPatch; │ │143 + const revision = (try evt.PatchRev.readById(DB, hash_kind, haxy_moment.readOnly(), arena, &id)) orelse return error.InvalidPatch; │ │144 + if (!selected.matches(revision)) return error.InvalidPatch; │ │145 } │ │146 │ │147 const order_key = evt.orderKeyDesc(record.created_order, event_id); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │151 const old_status = try statusSet(DB, statuses, existing.event.status); │ │152 _ = try old_status.remove(&order_key); │ │153 try removeFromTagSets(DB, tag_statuses, existing.event.tags, existing.event.status, &order_key); │ │154 + if (existing.event.status != .merged) { │ │155 + if (existing.event.revision) |revision| { │ │156 + const revisions = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, revision_to_id_set_key)));│ │157 + for ([_][]const u8{ revision.squash_oid, revision.source_oid }, 0..) |oid, i| { │ │158 + if (i != 0 and std.mem.eql(u8, revision.squash_oid, revision.source_oid)) continue; │ │159 + const key_hash = hash.hashInt(hash_kind, try revisionKey(arena.allocator(), revision.target_ref, oid)); │ │160 + const ids = try DB.CountedHashSet(.read_write).init(try revisions.putCursor(key_hash)); │ │161 + _ = try ids.remove(record_key); │ │162 + if (try ids.count() == 0) _ = try revisions.remove(key_hash); │ │163 + } │ │164 + } │ │165 + } │ │166 } │ │167 } │ │168 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │192 var key_buffer: TagStatusKey = undefined; │ │193 const set = try DB.SortedSet(.read_write).init(try tag_statuses.putCursor(try tagStatusKey(&key_buffer, tag, record.event.status)));│ │194 try set.put(&order_key); │ │195 + } │ │196 + │ │197 + if (record.event.status != .merged) { │ │198 + if (record.event.revision) |revision| { │ │199 + const revisions = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, revision_to_id_set_key))); │ │200 + for ([_][]const u8{ revision.squash_oid, revision.source_oid }, 0..) |oid, i| { │ │201 + if (i != 0 and std.mem.eql(u8, revision.squash_oid, revision.source_oid)) continue; │ │202 + const key = try revisionKey(arena.allocator(), revision.target_ref, oid); │ │203 + const ids = try DB.CountedHashSet(.read_write).init(try revisions.putCursor(hash.hashInt(hash_kind, key))); │ │204 + try ids.put(record_key, .{ .bytes = event_id }); │ │205 + } │ │206 + } │ │207 } │ │208 } │ │209 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │215 outcome: *[std.meta.fields(Self).len]evt.FieldMerge, │ │216 ) void { │ │217 const status_index = std.meta.fieldIndex(Self, "status") orelse @compileError("Patch.status not found"); │ │164 - const patchrev_index = std.meta.fieldIndex(Self, "patchrev_id") orelse @compileError("Patch.patchrev_id not found");│ │165 - if (outcome[status_index] != .conflicted or outcome[patchrev_index] == .conflicted) return; │ │218 + const revision_index = std.meta.fieldIndex(Self, "revision") orelse @compileError("Patch.revision not found"); │ │219 + if (outcome[status_index] != .conflicted or outcome[revision_index] == .conflicted) return; │ │220 │ │167 - if (target.status == .merged and std.meta.eql(target.patchrev_id, merged.patchrev_id)) { │ │221 + if (target.status == .merged and evt.fieldEqual(?Revision, target.revision, merged.revision)) { │ │222 merged.status = .merged; │ │223 outcome[status_index] = .kept; │ │170 - } else if (parent.status == .merged and std.meta.eql(parent.patchrev_id, merged.patchrev_id)) { │ │224 + } else if (parent.status == .merged and evt.fieldEqual(?Revision, parent.revision, merged.revision)) { │ │225 merged.status = .merged; │ │226 outcome[status_index] = .parent; │ │227 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │228 } │ │229 │ │230 +fn revisionKey(allocator: std.mem.Allocator, target_ref: []const u8, oid: []const u8) ![]u8 {│ │231 + return std.mem.concat(allocator, u8, &.{ target_ref, "\x00", oid }); │ │232 +} │ │233 + │ │234 fn statusSet( │ │235 comptime DB: type, │ │236 statuses: DB.SortedMap(.read_write), │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯