╭ changed ──────────╮ │src/event/Patch.zig│ ╰───────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────╮ │ 9 title: []const u8, │ │10 description: []const u8, │ │11 tags: []const u8, // space-separated │ │12 +source_branch: ?[]const u8 = null, │ │13 target_branch: []const u8, │ │14 target_patch_id: ?[evt.event_id_size * 2]u8 = null,│ │15 revision: ?Revision = null, │ ╰───────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │134 pub const status_to_id_set_key = "status->patch-id-set"; │ │135 pub const tag_status_to_id_set_key = "tag+status->patch-id-set"; │ │136 pub const revision_to_id_set_key = "target-branch+oid->patch-id-set"; │ │137 +pub const source_to_id_set_key = "source-branch->patch-id-set"; │ │138 │ │139 pub const TagStatusKey = [tag_max_len + 1 + StatusKind.longest_len]u8;│ │140 │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │181 │ │182 if (!fieldsValid(record.event.title, record.event.tags)) return error.InvalidPatch; │ │183 if (!rf.validateName(record.event.target_branch)) return error.InvalidTarget; │ │184 + if (record.event.source_branch) |branch| { │ │185 + if (!rf.validateName(branch) or std.mem.eql(u8, branch, evt.events_ref.name) or │ │186 + std.mem.eql(u8, record.event.target_branch, evt.events_ref.name)) return error.InvalidSourceBranch;│ │187 + } │ │188 const revision_id = if (record.event.revision) |*revision| blk: { │ │189 try evt.PatchRev.validateOid(hash_kind, revision.squash_oid); │ │190 try evt.PatchRev.validateOid(hash_kind, revision.source_oid); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │197 const status_kind = record.event.status.kind(); │ │198 │ │199 if (existing_maybe) |existing| { │ │200 + if (!evt.fieldEqual(?[]const u8, existing.event.source_branch, record.event.source_branch)) return error.PatchSourceChanged;│ │201 record.created_order = existing.created_order; │ │202 record.author_email = existing.author_email; │ │203 const existing_status_kind = existing.event.status.kind(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │242 } │ │243 } │ │244 } │ │245 + } │ │246 + } │ │247 + │ │248 + if (record.event.source_branch) |branch| { │ │249 + const sources = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, source_to_id_set_key)));│ │250 + if (!record.removed and status_kind != .merged) { │ │251 + const ids = try DB.CountedHashSet(.read_write).init(try sources.putCursor(branch)); │ │252 + try ids.put(record_key, .{ .bytes = event_id }); │ │253 + } else if (try sources.getCursor(branch) != null) { │ │254 + const ids = try DB.CountedHashSet(.read_write).init(try sources.putCursor(branch)); │ │255 + _ = try ids.remove(record_key); │ │256 + if (try ids.count() == 0) _ = try sources.remove(branch); │ │257 } │ │258 } │ │259 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │345 }; │ │346 }, │ │347 .fields => |fields| { │ │329 - if (!rf.validateName(fields.target_branch)) return error.InvalidFields; │ │330 - if ((try repo.readRef(io, .{ .kind = .head, .name = fields.target_branch })) == null) return error.InvalidFields; │ │348 + if (!rf.validateName(fields.target_branch)) return error.InvalidTargetBranch; │ │349 + if ((try repo.readRef(io, .{ .kind = .head, .name = fields.target_branch })) == null) return error.InvalidTargetBranch;│ │350 updated.title = fields.title; │ │351 updated.tags = fields.tags; │ │352 updated.description = fields.description; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │359 } │ │360 if (!fieldsValid(updated.title, updated.tags)) return error.InvalidFields; │ │361 │ │362 + if (updated.source_branch != null and updated.revision == null) return @import("../patch.zig").writeBranchPatch(host_kind, repo_kind, repo_opts, io, allocator, repo, std.fmt.bytesToHex(id.*, .lower), updated, record.event, author);│ │363 try evt.consume(host_kind, .repo, repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &.{.{ │ │364 .id = std.fmt.bytesToHex(id.*, .lower), │ │365 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────╮ │436 return false; │ │437 } │ │438 │ │419 -fn readFromRepo( │ │439 +pub fn readFromRepo( │ │440 comptime repo_kind: rp.RepoKind, │ │441 comptime repo_opts: rp.RepoOpts(repo_kind),│ │442 io: std.Io, │ ╰────────────────────────────────────────────────────╯